You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kchart/kdchart/KDChartBarPainter.cpp

1708 lines
115 KiB

/* -*- Mode: C++ -*-
KDChart - a multi-platform charting engine
*/
/****************************************************************************
** Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. All rights reserved.
**
** This file is part of the KDChart library.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid commercial KDChart licenses may use this file in
** accordance with the KDChart Commercial License Agreement provided with
** the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.klaralvdalens-datakonsult.se/?page=products for
** information about KDChart Commercial License Agreements.
**
** Contact info@klaralvdalens-datakonsult.se if any conditions of this
** licensing are not clear to you.
**
**********************************************************************/
#include "KDChartBarPainter.h"
#include <KDChartParams.h>
#include <qpainter.h>
#include <stdlib.h>
/**
\class KDChartBarPainter KDChartBarPainter.h
\brief A chart painter implementation that can paint bar charts.
*/
/**
Constructor. Sets up internal data structures as necessary.
\param params the KDChartParams structure that defines the chart
*/
KDChartBarPainter::KDChartBarPainter( KDChartParams* params ) :
KDChartAxesPainter( params )
{
// This constructor intentionally left blank so far; we cannot setup the
// geometry yet since we do not know the size of the painter.
}
/**
Destructor.
*/
KDChartBarPainter::~KDChartBarPainter()
{
// intentionally left blank
}
bool KDChartBarPainter::isNormalMode() const
{
return KDChartParams::BarNormal == params()->barChartSubType();
}
int KDChartBarPainter::clipShiftUp( bool normalMode, double areaWidthP1000 ) const
{
const bool bThreeDBars = params()->threeDBars() || (KDChartParams::BarMultiRows == params()->barChartSubType());
return ( normalMode && !bThreeDBars )
? static_cast < int > ( areaWidthP1000 * 16.0 )
: 0;
}
void KDChartBarPainter::initMyPainter( QPainter* painter )
{
_myPainter = painter;
_painterDX = 0.0;
_painterDY = 0.0;
}
void KDChartBarPainter::shiftMyPainter( double dx, double dy )
{
if( dx != 0.0 || dy != 0.0 ){
_myPainter->translate(dx, dy);
_painterDX += dx;
_painterDY += dy;
}
}
void KDChartBarPainter::shiftMyPainterBack()
{
if( _painterDX != 0.0 || _painterDY != 0.0 ){
_myPainter->translate(-_painterDX, -_painterDY);
_painterDX = 0.0;
_painterDY = 0.0;
}
}
void KDChartBarPainter::calculateXFront1_2( bool bNormalMode, bool bIsVeryFirstBar, bool bIsFirstDataset, bool _bThreeDBars,
double xpos, double valueBlockGap, double datasetGap, double frontBarWidth,
int& frontX1, int& frontX2, int& prevFrontX2 ){
if( _bThreeDBars || bIsVeryFirstBar || !bNormalMode )
frontX1 = static_cast<int>( xpos );
else if( bIsFirstDataset )
frontX1 = prevFrontX2 + static_cast <int> ( valueBlockGap ) + 1;
else
frontX1 = prevFrontX2 + static_cast < int > ( datasetGap ) + 1;
// Note: frontX2 is calculated by using xpos but NOT by using frontX1.
// So we make sure that (a) the gaps between all bars of one dataset
// are exactly the same size, and that (b) the bars are automatically
// adjusted in their width: to match their very position within the abscissa range.
// Adjusting will result in them being ONE pixel wider sometimes.
frontX2 = static_cast < int > ( xpos + frontBarWidth );
prevFrontX2 = frontX2;
}
QPointArray rectToPointArray( const QRect& r )
{
QPointArray a(4);
a.putPoints( 0, 4, r.left(),r.top(), r.right(),r.top(), r.right(),r.bottom(), r.left(),r.bottom() );
return a;
}
void KDChartBarPainter::specificPaintData( QPainter* painter,
const QRect& ourClipRect,
KDChartTableDataBase* data,
KDChartDataRegionList* regions,
const KDChartAxisParams* ordinatePara,
bool bNormalMode,
uint chart,
double logWidth,
double areaWidthP1000,
double logHeight,
double axisYOffset,
double minColumnValue,
double maxColumnValue,
double columnValueDistance,
uint chartDatasetStart,
uint chartDatasetEnd,
uint datasetStart,
uint datasetEnd )
{
/****************Pending Michel***********************
*Rectangles are painted in the following order: *
*front , right side, top. *
*Store the painted rectangles in order to paint *
*them in relation to each other for more precision. *
****************************************************/
QRect frontRectPositive,frontRectNegative,
rightRectPositive, rightRectNegative,
excessRectPositive,excessRectNegative;
_areaP1000 = areaWidthP1000;
if( !data ) return;
// qDebug("datasetStart: %i datasetEnd: %i", datasetStart, datasetEnd);
const QPen defaultOutlinePen( params()->outlineDataColor(),
params()->outlineDataLineWidth(),
params()->outlineDataLineStyle() );
abscissaInfos ai;
ai.bCenterThePoints = true;
calculateAbscissaInfos( *params(), *data,
datasetStart, datasetEnd,
logWidth, _dataRect,
ai );
const QRect devRect( painter->window() );
initMyPainter( painter );
const bool ordinateIsLogarithmic
= KDChartAxisParams::AxisCalcLogarithmic == ordinatePara->axisCalcMode();
const bool bMultiRows = KDChartParams::BarMultiRows == params()->barChartSubType();
_bThreeDBars = params()->threeDBars() || bMultiRows;
int numChartDataEntryDatasets = 0;
uint myLastDataEntryDataset = 0;
for( uint iD = chartDatasetStart; iD <= chartDatasetEnd; ++iD ){
if( params()->chartSourceMode( iD ) == KDChartParams::DataEntry ){
++numChartDataEntryDatasets;
myLastDataEntryDataset = iD;
}
}
const bool bHadClipping = painter->hasClipping();
//this allow the height/size of the bar to be painted fully
if( bMultiRows ) {
painter->setClipping( false );
}
// Number of columns in one dataset: If -1, use all values,
// otherwise use the specified number of values.
int numValues = 0;
if ( params()->numValues() != -1 )
numValues = params()->numValues();
else
numValues = data->usedCols();
double datasetGap = bMultiRows
? 0.0
: params()->datasetGap()
* ( params()->datasetGapIsRelative()
? areaWidthP1000
: 1.0 );
double valueBlockGap = bMultiRows
? 0.0
: params()->valueBlockGap()
* ( params()->valueBlockGapIsRelative()
? areaWidthP1000
: 1.0 );
// This is the same for all three bar types except for multi-bar Surface charts.
double spaceBetweenValueBlocks = bMultiRows
? 0.0
: static_cast<int>( valueBlockGap ) * numValues;
// Set some geometry values that apply to bar charts only
double totalNumberOfBars = 0.0;
double spaceBetweenDatasets = 0.0;
switch ( params()->barChartSubType() ) {
case KDChartParams::BarNormal: {
totalNumberOfBars = numChartDataEntryDatasets * numValues;
spaceBetweenDatasets = datasetGap
* ( totalNumberOfBars - numValues );
break;
}
case KDChartParams::BarStacked:
case KDChartParams::BarPercent:
case KDChartParams::BarMultiRows:
totalNumberOfBars = numValues;
spaceBetweenDatasets = 0; // always 0 when stacked/percent/multi-rows
break;
default:
qFatal( "Unsupported bar chart type" );
};
double barWidth = 0.0;
if( params()->barWidth() == KDCHART_AUTO_SIZE ) {
//Default auto calc. to optimize when many bars datasetGap == blockGap/2 == barWidth/2
//unless valueBlockGap or datasetGap are different from default values == users value
barWidth = logWidth / totalNumberOfBars;
do {
barWidth -= 0.1;
params()->valueBlockGap() != 24 ? spaceBetweenValueBlocks = params()->valueBlockGap() * numValues
: spaceBetweenValueBlocks = ( barWidth/2 ) * numValues;
if ( spaceBetweenDatasets != 0 )
params()->datasetGap()!= 6 ? spaceBetweenDatasets = params()->datasetGap() * ( totalNumberOfBars - numValues )
: spaceBetweenDatasets = ( barWidth/4 ) * ( totalNumberOfBars - numValues );
}
while ( barWidth*totalNumberOfBars + spaceBetweenValueBlocks + spaceBetweenDatasets > logWidth );
valueBlockGap = ( spaceBetweenValueBlocks )/ numValues;
if ( spaceBetweenDatasets != 0 )
datasetGap = spaceBetweenDatasets / ( totalNumberOfBars - numValues );
barWidth = (logWidth - ( valueBlockGap * numValues ) - ( datasetGap * ( totalNumberOfBars - numValues ) ) ) / totalNumberOfBars;
;
/* Debug space available
qDebug ( " logWidth %f" , logWidth );
qDebug ( "compare value %f", barWidth*totalNumberOfBars + spaceBetweenValueBlocks + datasetGap * ( totalNumberOfBars - numValues ) );
*/
} else if( 0 > params()->barWidth() )
barWidth = params()->barWidth() * -areaWidthP1000;
else
barWidth = params()->barWidth();
//sideBarWidth: pre-calculate in order to have a reference
//we will correct according to the available space later.
//This in order to center the labels in relation to the front width
//when painting 3DBars
double sideBarWidth = _bThreeDBars
? ( barWidth - barWidth / (1.0 + params()->cosThreeDBarAngle()) ) *
params()->threeDBarDepth()
: 0.0;
const double frontBarWidth = _bThreeDBars && !bMultiRows
? barWidth - sideBarWidth
: barWidth;
//correct the width for the side of the bar
double totalThreeDBarWidth = totalNumberOfBars*barWidth + sideBarWidth;
double totalSpaceOccupied = totalThreeDBarWidth + spaceBetweenDatasets + spaceBetweenValueBlocks;
if ( logWidth < totalSpaceOccupied) {
sideBarWidth -= (totalSpaceOccupied /*+ spaceBetweenDatasets + spaceBetweenValueBlocks*/ - logWidth)/totalNumberOfBars;
//qDebug ( "logWidth %f", logWidth );
//qDebug ( "totalSpaceOccupied %f", totalSpaceOccupied );
}
const double sideBarHeight = sideBarWidth;
double pixelsPerUnit = 0.0;
if ( params()->barChartSubType() != KDChartParams::BarPercent ) // not used for percent
pixelsPerUnit = logHeight / ( columnValueDistance ? columnValueDistance : 10 );
//qDebug("\nordinatePara->trueAxisLow() = %f", ordinatePara->trueAxisLow());
//qDebug( "ordinatePara->trueAxisHigh() = %f", ordinatePara->trueAxisHigh());
//qDebug( "sideBarHeight = %f", sideBarHeight);
//qDebug( "pixelsPerUnit = %f", pixelsPerUnit);
double zeroXAxisI;
if ( params()->barChartSubType() == KDChartParams::BarPercent ) {
if ( minColumnValue == 0.0 )
zeroXAxisI = 0.0;
else if ( maxColumnValue == 0.0 )
zeroXAxisI = logHeight - sideBarHeight;
else
zeroXAxisI = ( logHeight - sideBarHeight ) / 2.0;
} else {
zeroXAxisI = logHeight
- ordinatePara->axisZeroLineStartY()
+ _dataRect.y();
}
double shiftUpperBars = (params()->barChartSubType() != KDChartParams::BarPercent)
&& (ordinatePara->axisTrueLineWidth() % 2)
? 1.0
: 0.0;
// Initializing drawing positions
double yposPositivesStart = logHeight;
double yposNegativesStart = logHeight;
if( params()->barChartSubType() == KDChartParams::BarPercent ){
yposPositivesStart += axisYOffset;
yposNegativesStart += axisYOffset;
}
for ( int iPaintExtraLinesOrTheData = 0;
iPaintExtraLinesOrTheData < 3;
++iPaintExtraLinesOrTheData )
{
const bool bDrawExtraLines = (1 != iPaintExtraLinesOrTheData);
const bool bDrawExtraLinesInFront = (2 == iPaintExtraLinesOrTheData);
double xpos = 0.0;
if ( _bThreeDBars && !bMultiRows )
xpos = 0.0 + (barWidth/2) + (valueBlockGap/2) - (frontBarWidth/2);
else
xpos = 0.0 + (valueBlockGap / 2.0);
double yposPositives = yposPositivesStart;
double yposNegatives = yposNegativesStart;
/* Pending Michel: no need for this anymore */
//double totalThreeDBarWidth = bMultiRows
//? barWidth + sideBarWidth
// : barWidth;
double nShiftX = bMultiRows
? sideBarWidth
: 0.0;
double nShiftY = bMultiRows
? sideBarHeight
: 0.0;
double valueTotal = 0.0; // valueTotal is used for percent bars only
// iterate over all columns: item1, item2, item3 ...
int prevFrontX2 = 0;
bool bIsVeryFirstBar = true;
for ( int value = 0; value < numValues; ++value ) {
bool bFirstValidValueUnknown = true;
uint firstValidValue = 0;
uint lastValidPositiveValue = 0;
double maxValueInThisColumn = 0.0, minValueInThisColumn = 0.0;
if ( params()->barChartSubType() == KDChartParams::BarStacked ||
params()->barChartSubType() == KDChartParams::BarPercent) {
valueTotal = 0.0;
// iterate over datasets of this axis only:
for ( uint dataset = datasetStart;
dataset <= datasetEnd;
++dataset ) {
QVariant vVal;
if( data->cellCoord( dataset, value, vVal, 1 )
&& params()->chartSourceMode( dataset ) == KDChartParams::DataEntry
&& QVariant::Double == vVal.type() ){
const double cellValue
= ordinateIsLogarithmic
? log10( vVal.toDouble() )
: vVal.toDouble();
//qDebug("value %u dataset %u logHeight %f", value,dataset,logHeight);
if( bFirstValidValueUnknown ){
firstValidValue = dataset;
bFirstValidValueUnknown = false;
}
if( 0.0 <= cellValue )
lastValidPositiveValue = dataset;
maxValueInThisColumn = QMAX( maxValueInThisColumn, cellValue );
minValueInThisColumn = QMIN( minValueInThisColumn, cellValue );
if( params()->barChartSubType() == KDChartParams::BarPercent /*||
params()->barChartSubType() == KDChartParams::BarStacked*/ )
valueTotal += cellValue;
}
}
}
//qDebug("shiftMyPainter( (numChartDataEntryDatasets-1)*nShiftX, (numChartDataEntryDatasets-1)*-nShiftY );");
shiftMyPainter( (numChartDataEntryDatasets-1)*nShiftX, (numChartDataEntryDatasets-1)*-nShiftY );
// iterate over all datasets of this chart:
// (but draw only the bars of this axis)
bool bIsFirstDataset = true;
for ( uint dataset = bMultiRows
? chartDatasetEnd
: chartDatasetStart;
dataset >= chartDatasetStart && dataset <= chartDatasetEnd;
bMultiRows
? --dataset
: ++dataset ) {
//qDebug("value %u dataset %u logHeight %f", value,dataset,logHeight);
const bool bDataEntrySourceMode
= (params()->chartSourceMode( dataset ) == KDChartParams::DataEntry);
QVariant coord1;
QVariant coord2;
int propID;
if( data->cellContent( dataset, value, coord1, coord2, propID )
&& QVariant::Double == coord1.type() ){
const double cellValue
= ordinateIsLogarithmic
? log10( coord1.toDouble() )
: coord1.toDouble();
//qDebug("b");
// there is a numeric value
double barHeight;
if ( params()->barChartSubType() == KDChartParams::BarPercent )
barHeight = ( cellValue / valueTotal ) * fabs(zeroXAxisI - logHeight );
else {
barHeight = pixelsPerUnit * cellValue;
if( 0.0 <= barHeight )
//barHeight = QMAX(0.0, barHeight - sideBarHeight);
barHeight = barHeight - sideBarHeight;
else
barHeight -= sideBarHeight;
}
// This little adjustment avoids a crash when trying
// to retrieve the bounding rect of a zero size region...
if( 0 == barHeight || 0.0 == barHeight ) {
barHeight = 1.0; // makes sense to have a min size anyway
}
// draw only the bars belonging to the axis
// which we are processing currently
if( dataset >= datasetStart && dataset <= datasetEnd ) {
//qDebug("b2");
// calculate Abscissa axis value, if there are X coordinates
// ---------------------------------------------------------
bool skipMe = false;
if( ai.bCellsHaveSeveralCoordinates ){
skipMe = !calculateAbscissaAxisValue( coord2,
ai, 0, xpos );
// adjust bar position to have it horizontally centered to the point
if( ai.bAbscissaHasTrueAxisDtValues &&
QVariant::DateTime == coord2.type() )
xpos -= frontBarWidth / 2.0;
}
if( !skipMe ){
// Configure colors
QColor myBarColor( params()->dataColor( dataset ) );
QColor myShadow1Color( params()->dataShadow1Color( dataset ) );
QColor myShadow2Color( params()->dataShadow2Color( dataset ) );
// Get default values for extra lines and their markers
const KDChartParams::LineMarkerStyle
defaultMarkerStyle = params()->lineMarkerStyle( dataset );
const QPen defaultPen( params()->lineColor().isValid()
? params()->lineColor()
: params()->dataColor( dataset ),
params()->lineWidth(),
params()->lineStyle( dataset ) );
// --------------------------------------------------------
// determine any 'extra' properties assigned to this cell
// by traversing the property set chain (if necessary)
// --------------------------------------------------------
KDChartPropertySet propSet;
bool bCellPropertiesFound =
params()->calculateProperties( propID,
propSet );
bool bShowThisBar = bDataEntrySourceMode;
if( bCellPropertiesFound ){
if( bShowThisBar && !bDrawExtraLines ){
int iDummy;
if( propSet.hasOwnShowBar( iDummy, bShowThisBar ) ){
// NOP
}
if( propSet.hasOwnBarColor( iDummy, myBarColor ) ){
// adjust the shadow colors
params()->calculateShadowColors( myBarColor,
myShadow1Color,
myShadow2Color );
}
}
}
if( !bDrawExtraLines || bCellPropertiesFound ){
//QRegion* region = 0;
KDChartDataRegion::PointArrayList * pointArrayList = 0;
if( bDataEntrySourceMode && !bDrawExtraLines ){
painter->setPen( defaultOutlinePen );
if ( myBarColor.isValid() )
painter->setBrush( myBarColor );
else
painter->setBrush( NoBrush );
// Prepare region for detection of mouse clicks
// and for finding anchor positions of data value texts
if ( regions )
pointArrayList = new KDChartDataRegion::PointArrayList();
}
// Start drawing
int frontX1 = 0;
int frontX2 = 0;
bool tooLow = false;
bool tooHigh = false;
if ( cellValue < 0 || cellValue < minColumnValue ) {
double maxValueYPos = maxColumnValue * pixelsPerUnit;
double yZero = yposNegatives - zeroXAxisI - sideBarHeight;
tooLow = (barHeight - yZero + logHeight + axisYOffset) < 0;
/*we need to handle those values in case we have a negative view*/
if ( cellValue <= 0 && cellValue > maxColumnValue && minColumnValue < 0 && maxColumnValue < 0 )
tooLow = true;
if ( tooLow && ( bNormalMode || bMultiRows) ) {
double delta = 0.0125 * logHeight;
double height = -1*(-1.0 * (yZero + sideBarHeight) - 2 * delta);
double height1 = height - 3.0 * delta;
int yArrowGap = static_cast < int > ( 2.5 * delta );
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
frontX1, frontX2, prevFrontX2 );
const int xm = static_cast < int > ( (frontX1 + frontX2) / 2.0 );
QRect rect( ourClipRect );
rect.setHeight( static_cast<int>( rect.height() + 3.0 * delta ) );
painter->setClipRect( rect );
//Pending Michel: Make sure the point of the arrow is always at the same distance
//from the X axis reference to the point of the arrow.
int arrowXAxisGap;
QPoint arrowTop( xm,static_cast<int>( yZero + height1 + 2 * yArrowGap) );
if ( arrowTop.y()== yposNegatives )
arrowXAxisGap = -2;
else
arrowXAxisGap = static_cast <int> (yposNegatives - arrowTop.y() - 2);
if( bDrawExtraLines ){
drawExtraLinesAndMarkers(
propSet,
defaultPen,
defaultMarkerStyle,
xm, static_cast<int>( yZero + height1 ),
painter,
ai.abscissaPara,
ordinatePara,
areaWidthP1000,
logHeight/1000.0,
bDrawExtraLinesInFront );
}else if( bShowThisBar ){
if( params()->drawSolidExcessArrows() ) {
/* PENDING Michel:
* Here we have two situations.
* The value is too low because over the Min negative value
* or it is not within the configured view.
*/
// Draw solid excess arrows negatives
QPointArray points( 5 );
/*this works in a positive view -> 200 500*/
points.setPoint( 0, frontX1, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta)+(2*yArrowGap)+ arrowXAxisGap:
static_cast<int>( yZero + height1)+(2*yArrowGap)+ arrowXAxisGap);
points.setPoint( 1, frontX2, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta)+(2*yArrowGap)+ arrowXAxisGap:
static_cast<int>( yZero + height1)+(2*yArrowGap)+ arrowXAxisGap );
points.setPoint( 2, frontX2, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta)+(2*yArrowGap)+ arrowXAxisGap:
static_cast<int>( yZero + height1)+(2*yArrowGap)+ arrowXAxisGap);
points.setPoint( 3, xm, cellValue < 0 ?
static_cast<int>( yZero + height1)+(2*yArrowGap)+ arrowXAxisGap:
static_cast<int>(yZero+height1 - 3.0*delta)+(2*yArrowGap)+ arrowXAxisGap);
points.setPoint( 4, frontX1, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta)+(2*yArrowGap)+ arrowXAxisGap:
static_cast<int>( yZero + height1)+(2*yArrowGap)+ arrowXAxisGap);
/* 0 between start and end -> -500 500*/
if ( minColumnValue < 0 && maxColumnValue > 0 ) {
points.setPoint(0, points.point(0).x(), static_cast <int> (yposNegatives - zeroXAxisI) );
points.setPoint(1, points.point(1).x(), static_cast <int> (yposNegatives - zeroXAxisI) );
}
/* negative view -> -200 -500 */
if ( minColumnValue < 0 && maxColumnValue < 0 ) {
/*value negative or zero > maxColumnValue*/
if ( cellValue > maxColumnValue ) {
// the view is under Yaxis 0 level
// we need to show a symbol for the bars which are over the Yaxis.
// calculate the coordinate and direction for the arrow.
// arrow downward for negative values and upward for positives value
int diffArrowBase = points.point(2).y() - points.point(3).y();
double maxValueYPos = maxColumnValue * pixelsPerUnit;
double minValueYPos = minColumnValue * pixelsPerUnit;
double adjustedArrow = (cellValue == 0 ? minValueYPos - maxValueYPos + diffArrowBase + 2:
minValueYPos - maxValueYPos - diffArrowBase + 1);
points.setPoint( 0, frontX1, points.point(0).y() + static_cast <int> (adjustedArrow));
points.setPoint( 1, frontX2, points.point(1).y() + static_cast <int> (adjustedArrow ));
points.setPoint( 2, frontX2, points.point(2).y() + static_cast <int> (adjustedArrow));
points.setPoint( 3, xm, points.point(3).y() + static_cast <int> (adjustedArrow));
points.setPoint( 4, frontX1, points.point(4).y() + static_cast <int> (adjustedArrow));
}
/*value < mincolumn value*/
if ( cellValue < minColumnValue ) {
points.setPoint( 0, frontX1, static_cast <int> ( yZero - maxValueYPos ) );
points.setPoint( 1, frontX2, static_cast <int> ( yZero - maxValueYPos ) );
}
}
/*Pending Michel: case setbarWidth */
//adjust the painting in case we have a user given Width allow it
//to be larger than the auto calculated width in case we want to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
QRect tmpRect ( points.point(0), points.point(2));
points.setPoint(0, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(0).y());
points.setPoint(1, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(1).y());
points.setPoint(2, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(2).y());
points.setPoint(4, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(4).y());
//}
}
if ( bMultiRows )painter->setClipping( false );
painter->drawPolygon( points );
//correct the y position: displaying position for the value label
QPoint tpLeft (points.point(4).x(), points.point(4).y() - 2 * yArrowGap );
QPoint tpRight(points.point(2).x(), points.point(2).y() - 2 * yArrowGap );
//store the front rectangle
excessRectNegative.setTopLeft(tpLeft);
excessRectNegative.setTopRight(tpRight);
excessRectNegative.setBottomRight(points.point(1));
excessRectNegative.setBottomLeft(points.point(0));
// Don't use points for drawing after this!
if ( pointArrayList ) {
if ( cellValue < 0 ) {
//correction for labels vertical position
int size = static_cast <int> ( ( points.point( 3 ).y() - tpRight.y() + excessRectNegative.width() )/2 );
points.setPoint( 4 ,tpLeft );
points.setPoint( 2, tpRight );
if ( cellValue < maxColumnValue )
points.translate( _dataRect.x(), -_dataRect.y() - size );
else
points.translate( _dataRect.x(), _dataRect.y() );
} else
points.translate( _dataRect.x(), _dataRect.y() );
pointArrayList->append( points );
}
} else {
// Draw split excess arrows negatives
/* PENDING Michel:
* Here we have two situations.
* The value is too low because over the Min negative value
* The value is not within the configured view..
*/
QPointArray points( 5 );
/*this works in a positive view -> 200 500*/
points.setPoint( 0, frontX1, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta) + arrowXAxisGap:
static_cast<int>( yZero + height1)+(2 * yArrowGap)+ arrowXAxisGap);
points.setPoint( 1, frontX2, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta) + arrowXAxisGap:
static_cast<int>( yZero + height1)+(2 * yArrowGap)+ arrowXAxisGap);
points.setPoint( 2, frontX2, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta) + arrowXAxisGap:
static_cast<int>( yZero + height1)+(2 * yArrowGap)+ arrowXAxisGap);
points.setPoint( 3, xm, cellValue < 0 ?
static_cast<int>( yZero + height1) + arrowXAxisGap:
static_cast<int>(yZero+height1 - 3.0*delta)+(2 * yArrowGap)+ arrowXAxisGap);
points.setPoint( 4, frontX1, cellValue < 0 ?
static_cast<int>(yZero+height1 - 3.0*delta) + arrowXAxisGap:
static_cast<int>( yZero + height1)+(2 * yArrowGap)+ arrowXAxisGap);
/* 0 between start and end -> -500 500*/
if ( minColumnValue < 0 && maxColumnValue > 0 ) {
points.setPoint(0, points.point(0).x(), static_cast <int> (yposNegatives - zeroXAxisI) );
points.setPoint(1, points.point(1).x(), static_cast <int> (yposNegatives - zeroXAxisI) );
}
/* negative view -> -200 -500 */
if ( minColumnValue < 0 && maxColumnValue < 0 ) {
/*value negative or zero > maxColumnValue*/
if ( cellValue > maxColumnValue ) {
// the view is under Yaxis 0 level
// we need to show a symbol for the bars which are over the Yaxis.
// calculate the coordinate and direction for the arrow.
// arrow downward for negative values and upward for positives value
int diffArrowBase = points.point(2).y() - points.point(3).y();
double maxValueYPos = maxColumnValue * pixelsPerUnit;
points.setPoint( 0, frontX1, static_cast <int> ( yZero - maxValueYPos) );
points.setPoint( 1, frontX2, static_cast <int> ( yZero - maxValueYPos) );
points.setPoint( 2, frontX2, static_cast <int> ( yZero - maxValueYPos) );
points.setPoint( 3, xm, static_cast <int> ( yZero - maxValueYPos - diffArrowBase ) );
points.setPoint( 4, frontX1, static_cast <int> ( yZero - maxValueYPos) );
}
/*value < mincolumn value*/
if ( cellValue < minColumnValue ) {
points.setPoint( 0, frontX1, static_cast <int> ( yZero - maxValueYPos) );
points.setPoint( 1, frontX2, static_cast <int> ( yZero - maxValueYPos) );
}
}
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
QRect tmpRect ( points.point(0), points.point(2));
points.setPoint(0, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(0).y());
points.setPoint(1, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(1).y());
points.setPoint(2, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(2).y());
points.setPoint(4, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(4).y());
}
if ( bMultiRows )painter->setClipping( false );
painter->drawPolygon( points );
//correct the y position: displaying position for the value label
QPoint tpLeft (points.point(4).x(), points.point(4).y() - 2 * yArrowGap );
QPoint tpRight(points.point(2).x(), points.point(2).y() - 2 * yArrowGap );
//store the excess front rectangle
excessRectNegative.setTopLeft(tpLeft);
excessRectNegative.setTopRight(tpRight);
excessRectNegative.setBottomRight(points.point(1));
excessRectNegative.setBottomLeft(points.point(0));
// Don't use points for drawing after this!
if ( pointArrayList ) {
if ( cellValue < 0 ) {
//calculate correction for labels vertical position
int size = static_cast <int> ( ( points.point( 3 ).y() - tpRight.y() + excessRectNegative.width() )/2 );
if ( cellValue < maxColumnValue )
points.translate( _dataRect.x(), -_dataRect.y() - size );
else
points.translate( _dataRect.x(), _dataRect.y() + ( 2 * yArrowGap ) );
} else
points.translate( _dataRect.x(), -_dataRect.y() );
pointArrayList->append( points );
}
QPointArray points2( 6 );
points2.setPoint( 0, frontX1, cellValue < 0 ?
static_cast<int>( yZero + height1 - 3.0 * delta ) + arrowXAxisGap:
static_cast<int>(yZero + height1) + arrowXAxisGap);
points2.setPoint( 1, xm, cellValue < 0 ?
static_cast<int>(yZero + height1) + arrowXAxisGap:
static_cast<int>( yZero + height1 - 3.0 * delta ) + arrowXAxisGap);
points2.setPoint( 2, frontX2, cellValue < 0 ?
static_cast<int>(yZero + height1 - 3.0 * delta) + arrowXAxisGap:
static_cast<int>(yZero + height1) + arrowXAxisGap);
points2.setPoint( 3, frontX2, cellValue < 0 ?
static_cast<int>(yZero + height1 - 3.75 * delta) + arrowXAxisGap :
static_cast<int>(yZero + height1 - 0.75 * delta) + arrowXAxisGap);
points2.setPoint( 4, xm, cellValue < 0 ?
static_cast<int>(yZero + height1 - 0.75 * delta) + arrowXAxisGap:
static_cast<int>(yZero + height1 - 3.75 * delta) + arrowXAxisGap);
points2.setPoint( 5, frontX1, cellValue < 0 ?
static_cast<int>(yZero + height1 - 3.75 * delta) + arrowXAxisGap:
static_cast<int>(yZero + height1 - 0.75 * delta) + arrowXAxisGap);
points2.translate( 0, yArrowGap );
if ( minColumnValue < 0 && maxColumnValue < 0 && cellValue > maxColumnValue ) {
// the view is under Yaxis 0 level
// we need to show a symbol for the bars which are over the Yaxis.
// calculate the coordinate and direction for the arrow.
// arrow downward for negative values and upward for positives value
int diffArrowBase = points.point(2).y() - points.point(3).y();
double maxValueYPos = maxColumnValue * pixelsPerUnit;
double minValueYPos = minColumnValue * pixelsPerUnit;
double adjustedArrow = cellValue == 0 ? minValueYPos - maxValueYPos + diffArrowBase:
minValueYPos - maxValueYPos - diffArrowBase + 1;
points2.setPoint( 0, frontX1, points2.point(0).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
points2.setPoint( 1, xm, points2.point(1).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
points2.setPoint( 2, frontX2, points2.point(2).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
points2.setPoint( 3, frontX2, points2.point(3).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
points2.setPoint( 4, xm, points2.point(4).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
points2.setPoint( 5, frontX1, points2.point(5).y()
+ static_cast <int> (adjustedArrow - diffArrowBase + yArrowGap) );
}
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
points2.setPoint(0, excessRectNegative.topLeft().x(),points2.point(0).y());
points2.setPoint(2, excessRectNegative.topRight().x(),points2.point(2).y());
points2.setPoint(3, excessRectNegative.topRight().x(),points2.point(3).y());
points2.setPoint(5, excessRectNegative.topLeft().x(),points2.point(5).y());
}
painter->drawPolygon( points2 );
/*
NOTE: We do NOT want to store the region here, but
we use the front rectangle above in order to display the
data value labels inside the front rectangle.
Disadvantage: clicking onto these excess arrows, will not
result in a signal being sent, because KDChartWidget will
not notice, that the user has clicked onto the bar.
That's a minor drawback, compared to the gain of being
able to position the labels correctly.
*/
if ( cellValue < 0 )
points2.translate( 0, yArrowGap );
else
points2.translate( 0, -yArrowGap );
painter->drawPolygon( points2 );
/*
NOTE: We do NOT want to store the region here
(see comment above)
*/
}
}
painter->setClipRect( ourClipRect );
} /*if (tooLow && bNormalMode)*/
else {
//
// old code (sometimes not touching the grid):
//QRect rec( xpos, yZero, frontBarWidth, -barHeight );
//painter->drawRect( rec );
//
int pt1Y = static_cast < int > ( yZero - barHeight /*- sideBarsHeight*/ );
/*
if ( cellValue != 0 ) {
pt1Y = static_cast <int> (cellValue * pixelsPerUnit * -2);
qDebug( "value %s",QString::number(static_cast <int> (cellValue)).latin1());
qDebug( "pt1Y %s", QString::number(static_cast <int> (cellValue * pixelsPerUnit * -2)).latin1());
}
else
pt1Y = static_cast < int > ( yZero - barHeight);
*/
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
frontX1, frontX2, prevFrontX2 );
QPoint pt1( frontX1, pt1Y );
QPoint pt2( frontX2,
static_cast < int > ( yZero + sideBarHeight) );
if( 0.0 > maxColumnValue ){
pt2.setY(pt2.y() - static_cast < int > (pixelsPerUnit * maxColumnValue));
}
if( pt2.y() < pt1Y ) {
pt1.setY( pt2.y() );
pt2.setY( pt1Y );
}
if( pt2.x() < frontX1 ) {
pt1.setX( frontX2 );
pt2.setX( frontX1 );
}
if( bDrawExtraLines ){
int y = pt2.y();
if( _bThreeDBars )
y -= static_cast < int > ( sideBarHeight );
drawExtraLinesAndMarkers(
propSet,
defaultPen,
defaultMarkerStyle,
(frontX1+frontX2)/2, y,
painter,
ai.abscissaPara,
ordinatePara,
areaWidthP1000,
logHeight/1000.0,
bDrawExtraLinesInFront );
}else if( bShowThisBar ){
QSize siz( pt2.x() - pt1.x(),
pt2.y() - pt1.y() );
QRect rect( pt1, siz );
if( 1.5 > frontBarWidth ){
QPen oldPen( painter->pen() );
painter->setPen( QPen(painter->brush().color(), 0) );
painter->drawLine(pt1, QPoint(pt1.x(),pt2.y()));
painter->setPen( oldPen );
}else{
// store the front rect negative
if ( tooLow || cellValue < minColumnValue) {
frontRectNegative.setTopLeft(excessRectNegative.bottomLeft());
frontRectNegative.setTopRight(excessRectNegative.bottomRight());
frontRectNegative.setBottomRight(excessRectNegative.topRight());
frontRectNegative.setBottomLeft(excessRectNegative.topLeft());
} else {
frontRectNegative.setTopLeft(rect.topLeft());
frontRectNegative.setTopRight(rect.topRight());
frontRectNegative.setBottomRight(rect.bottomRight());
frontRectNegative.setBottomLeft(rect.bottomLeft());
if ( cellValue == 0 && params()->barChartSubType() == KDChartParams::BarPercent)
rect.setTop( rect.bottom());
}
//debug lines and points
/*
painter->drawLine( rect.topLeft(), rect.topRight());
painter->drawLine( rect.topRight(), rect.bottomRight());
painter->drawLine( rect.bottomRight(), rect.bottomLeft());
painter->drawLine( rect.bottomLeft(), rect.topLeft());
painter->drawText( frontRectNegative.topLeft(), "0f");
painter->drawText( frontRectNegative.topRight(), "1f");
painter->drawText( frontRectNegative.bottomRight(), "2f");
painter->drawText( frontRectNegative.bottomLeft(), "3f");
*/
//Pending Michel adjust the painting in case we have a user given Width
//and it is not larger than the auto calculated width
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
rect.setLeft( static_cast <int> (rect.center().x() - (userwidth/2)));
rect.setRight( static_cast <int> (rect.center().x() + (userwidth/2)));
rect.setWidth( static_cast <int> (userwidth) );
frontRectNegative.setLeft( tooLow || cellValue < minColumnValue ?
static_cast <int> (excessRectNegative.center().x() - (userwidth/2)):rect.left());
frontRectNegative.setRight( tooHigh ? static_cast <int> (excessRectNegative.center().x() + (userwidth/2)):rect.right());
}
//drawing the front size negative values
if ( cellValue != 0 && params()->barChartSubType() != KDChartParams::BarPercent) {
painter->setClipping( false );
painter->drawRect( rect );
}
}
// Don't use rect for drawing after this!
if ( pointArrayList ) {
rect.moveBy( _dataRect.x(), _dataRect.y() );
pointArrayList->append( rectToPointArray( rect ) );
}
}
}
} else {
//
// Positive values:
//
/*Pending Michel: all values under the min value are handled as negative*/
double maxValueYPos = maxColumnValue * pixelsPerUnit;
double minValueYPos = minColumnValue * pixelsPerUnit;
double minDataValueYPos = maxValueYPos - minValueYPos;
double yZero = yposPositives - zeroXAxisI;
//qDebug( "yposPositives %f - zeroXAxisI %f = %f" , yposPositives, zeroXAxisI, yZero );
//qDebug( "yZero %s", QString::number( yZero ).latin1());
//qDebug( "minDataValueYPos = %s", QString::number( minDataValueYPos).latin1());
//qDebug( "positive value %s", QString::number( cellValue).latin1());
//Pending Michel: draw the default split excess arrows
//when the front top of the 3d chart reach the max Y value
if(!_bThreeDBars)
tooHigh = ( barHeight > maxValueYPos*1.001 ) || ( cellValue < minColumnValue );
else {
//calculate the Y position for the top front line
//if it is over the max height pos - tooHigh becomes true
if ( params()->barChartSubType()!= KDChartParams::BarStacked ) {
int dataValueYPos = static_cast <int>( ( cellValue * pixelsPerUnit ) );
tooHigh = dataValueYPos > maxValueYPos;
} else {
tooHigh = maxValueInThisColumn > maxColumnValue;
}
}
if ( tooHigh && bNormalMode ||
tooHigh && params()->barChartSubType()== KDChartParams::BarStacked
|| tooHigh && bMultiRows ) {
double delta = -0.0125 * logHeight;
double height = -1.0 * yZero
- 2.0 * delta;
double height1 = height + -3.0 * delta;
int yArrowGap = static_cast < int > ( 2.5 * delta );
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
frontX1, frontX2, prevFrontX2 );
const int xm = static_cast < int > ( ( frontX1 + frontX2 ) / 2.0 );
QRect rect( ourClipRect );
rect.setTop( static_cast<int>( rect.top() + 3 * delta ) );
rect.setHeight( static_cast<int>( rect.height() - 3 * delta ) );
painter->setClipRect( rect );
if( bDrawExtraLines ){
drawExtraLinesAndMarkers(
propSet,
defaultPen,
defaultMarkerStyle,
xm, static_cast<int>( yZero + height1 ),
painter,
ai.abscissaPara,
ordinatePara,
areaWidthP1000,
logHeight/1000.0,
bDrawExtraLinesInFront );
}else if( bShowThisBar ){
if( params()->drawSolidExcessArrows() ) {
// Draw solid excess arrows
QPointArray points( 5 );
/*this works for positive config and 0 include config*/
points.setPoint( 0, frontX1,
(minDataValueYPos < static_cast <int> (yZero))?
static_cast <int> (minDataValueYPos-1):static_cast <int>(yZero));
points.setPoint( 1, frontX2,
(minDataValueYPos < static_cast <int> (yZero))?
static_cast <int> (minDataValueYPos-1):static_cast <int>(yZero));
points.setPoint( 2, frontX2, static_cast<int>( yZero + height1 - 3.0 * delta )
+ 2 * yArrowGap );
points.setPoint( 3, xm, static_cast<int>( yZero + height1 )
+ 2 * yArrowGap );
points.setPoint( 4, frontX1, static_cast<int>( yZero + height1 - 3.0 * delta )
+ 2 * yArrowGap );
/*case where start and end value are negatives */
if ( cellValue > maxColumnValue && 0 >= maxColumnValue ) {
points.setPoint( 0, frontX1,static_cast<int>( yZero + height1 - 3.0 * delta ) + 2 * yArrowGap);
points.setPoint( 1, frontX2,static_cast<int>( yZero + height1 - 3.0 * delta ) + 2 * yArrowGap);
}
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
QRect tmpRect ( points.point(0), points.point(2));
points.setPoint(0, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(0).y());
points.setPoint(1, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(1).y());
points.setPoint(2, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(2).y());
points.setPoint(4, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(4).y());
//}
}
if ( params()->barChartSubType() != KDChartParams::BarStacked ||
params()->barChartSubType() == KDChartParams::BarStacked && dataset != datasetEnd ) {
//drawing a single front in case it is too high
painter->setClipping( false );
painter->drawPolygon( points );
}
//correct the y position: displaying position for the value label
QPoint tpLeft (points.point(4).x(), static_cast <int> (points.point(4).y() - yArrowGap));
QPoint tpRight(points.point(2).x(), static_cast <int> (points.point(2).y() - yArrowGap));
//debugging points
/*
painter->drawText( points.point(0), "p0");
painter->drawText( points.point(1), "p1");
painter->drawText( points.point(2), "p2");
painter->drawText( points.point(3), "p3");
*/
//store the front rectangle
excessRectPositive.setTopLeft(tpLeft);
excessRectPositive.setTopRight(tpRight);
excessRectPositive.setBottomRight(points.point(1));
excessRectPositive.setBottomLeft(points.point(0));
// Don't use points for drawing after this!
if ( pointArrayList && params()->barChartSubType() != KDChartParams::BarStacked ) {
points.translate( _dataRect.x(), _dataRect.y() + excessRectPositive.top() - yArrowGap );
pointArrayList->append( points );
} else if ( params()->barChartSubType() == KDChartParams::BarStacked ) {
if ( dataset != datasetEnd ) {
points.translate( _dataRect.x(), _dataRect.y() + excessRectPositive.top() );
pointArrayList->append( points );
} else {
//adjust the display of the value label under Y max value level
points.translate( _dataRect.x(), _dataRect.y() - excessRectPositive.bottom() - yArrowGap);
pointArrayList->append( points );
}
}
} else {
// Draw split excess arrows (default)
/* PENDING Michel:
* Here we have two situations.
* The value is too high because over the Max positive value
* or it is not within the configured view.
*/
QPointArray points( 5 );
/*this works for positive config and 0 include config*/
points.setPoint( 0, frontX1,
(minDataValueYPos < static_cast <int> (yZero))?
static_cast <int> (minDataValueYPos - 1) : static_cast <int>(yZero));
points.setPoint( 1, frontX2,
(minDataValueYPos < static_cast <int> (yZero))?
static_cast<int> ( minDataValueYPos - 1) : static_cast <int>(yZero));
points.setPoint( 2, frontX2, static_cast<int>( yZero + height1 - 3.0 * delta ) );
points.setPoint( 3, xm, static_cast<int>( yZero + height1 ) );
points.setPoint( 4, frontX1, static_cast<int>( yZero + height1 - 3.0 * delta ) );
/*case where start and end value are negatives */
if ( cellValue > maxColumnValue && 0 >= maxColumnValue || cellValue == 0) {
points.setPoint( 0, frontX1, static_cast<int>( yZero + height1 - 3.0 * delta ));
points.setPoint( 1, frontX2, static_cast<int>( yZero + height1 - 3.0 * delta ));
}
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
QRect tmpRect ( points.point(0), points.point(2));
points.setPoint(0, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(0).y());
points.setPoint(1, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(1).y());
points.setPoint(2, static_cast <int>(tmpRect.center().x() + userwidth/2),
points.point(2).y());
points.setPoint(4, static_cast <int>(tmpRect.center().x() - userwidth/2),
points.point(4).y());
//}
}
if ( params()->barChartSubType() != KDChartParams::BarStacked ||
params()->barChartSubType() == KDChartParams::BarStacked && dataset != datasetEnd ) {
painter->setClipping( false );
painter->drawPolygon( points );
}
//store the front rectangle
excessRectPositive.setTopLeft(points.point(4));
excessRectPositive.setTopRight(points.point(2));
excessRectPositive.setBottomRight(points.point(1));
excessRectPositive.setBottomLeft(points.point(0));
// Don't use points for drawing after this!
if ( pointArrayList && params()->barChartSubType() != KDChartParams::BarStacked ) {
points.translate( _dataRect.x(), _dataRect.y() + excessRectPositive.top() );
pointArrayList->append( points );
} else if ( params()->barChartSubType() == KDChartParams::BarStacked ) {
if ( dataset != datasetEnd ) {
points.translate( _dataRect.x(), _dataRect.y() + excessRectPositive.top() );
pointArrayList->append( points );
} else {
//adjust the display of the value label under Y max value level
points.translate( _dataRect.x(), _dataRect.y() - excessRectPositive.bottom() - yArrowGap);
pointArrayList->append( points );
}
}
QPointArray points2( 6 );
points2.setPoint( 0, frontX1, static_cast<int>( yZero + height1 - 3.0 * delta ) );
points2.setPoint( 1, xm, static_cast<int>( yZero + height1 ) );
points2.setPoint( 2, frontX2, static_cast<int>( yZero + height1 - 3.0 * delta ) );
points2.setPoint( 3, frontX2, static_cast<int>( yZero + height1 - 3.75 * delta ) );
points2.setPoint( 4, xm, static_cast<int>( yZero + height1 - 0.75 * delta ) );
points2.setPoint( 5, frontX1, static_cast<int>( yZero + height1 - 3.75 * delta ) );
points2.translate( 0, yArrowGap );
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
points2.setPoint(0, excessRectPositive.topLeft().x(),points2.point(0).y());
points2.setPoint(2, excessRectPositive.topRight().x(),points2.point(2).y());
points2.setPoint(3, excessRectPositive.topRight().x(),points2.point(3).y());
points2.setPoint(5, excessRectPositive.topLeft().x(),points2.point(5).y());
//}
}
painter->drawPolygon( points2 );
/*
NOTE: We do NOT want to store the region here, but
we use the front rectangle above in order to display the
data value labels inside the front rectangle.
Disadvantage: clicking onto these excess arrows, will not
result in a signal being sent, because KDChartWidget will
not notice, that the user has clicked onto the bar.
That's a minor drawback, compared to the gain of being
able to position the labels correctly.
*/
points2.translate( 0, yArrowGap );
painter->drawPolygon( points2 );
/*
NOTE: We do NOT want to store the region here
(see comment above)
*/
} // draw split excess arrow
} //if( bShowThisBar )
painter->setClipRect( ourClipRect );
} // not tooLow && bNormalMode )
else {
//bool fromBottom = bNormalMode && !_bThreeDBars;
double y0 = yposPositives - zeroXAxisI;
int pt1Y = static_cast < int > ( y0 - barHeight - sideBarHeight);
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
frontX1, frontX2, prevFrontX2 );
QPoint pt1( frontX1, pt1Y );
QPoint pt2( frontX2,
static_cast < int > ( y0 + shiftUpperBars ) );
if( 0.0 < minColumnValue )
pt2.setY(pt2.y() - static_cast < int > ( pixelsPerUnit * minColumnValue ));
if( pt2.y() < pt1Y ) {
pt1.setY( pt2.y() );
pt2.setY( pt1Y );
}
if( pt2.x() < frontX1 ) {
pt1.setX( frontX2 );
pt2.setX( frontX1 );
}
if( bDrawExtraLines ){
int y = pt1.y();
if( _bThreeDBars )
y -= static_cast < int > ( sideBarHeight );
drawExtraLinesAndMarkers(
propSet,
defaultPen,
defaultMarkerStyle,
(frontX1+frontX2)/2, y,
painter,
ai.abscissaPara,
ordinatePara,
areaWidthP1000,
logHeight/1000.0,
bDrawExtraLinesInFront );
}else if( bShowThisBar ){
QSize siz( pt2.x() - pt1.x(),
pt2.y() - pt1.y());
QRect rect( pt1, siz );
//adjust display for the 3d percent bars - last dataset.
if ( params()->barChartSubType() == KDChartParams::BarPercent && cellValue != 0)
rect.setTop( rect.top() + static_cast <int> (sideBarHeight) - 1);
// store the front rect
if( tooHigh ) {
frontRectPositive.setTopLeft(excessRectPositive.topLeft());
frontRectPositive.setTopRight(excessRectPositive.topRight());
frontRectPositive.setBottomRight(excessRectPositive.bottomRight());
frontRectPositive.setBottomLeft(excessRectPositive.bottomLeft());
} else {
frontRectPositive.setTopLeft(rect.topLeft());
frontRectPositive.setTopRight(rect.topRight());
frontRectPositive.setBottomRight(rect.bottomRight());
frontRectPositive.setBottomLeft(rect.bottomLeft());
if( _bThreeDBars && cellValue == 0 )
frontRectNegative = frontRectPositive;
}
if( 1.5 > frontBarWidth ){
//qDebug("1.5 > frontBarWidth ");
QPen oldPen( painter->pen() );
painter->setPen( QPen(painter->brush().color(), 0) );
painter->drawLine(pt1, QPoint(pt1.x(),pt2.y()));
painter->setPen( oldPen );
}else{
//debugging points and lines
/*
painter->drawText( rect.topLeft(), QString::number(cellValue).latin1());
painter->drawText( rect.topRight(), "1f");
painter->drawText( rect.bottomRight(), "2f");
painter->drawText( rect.bottomLeft(), "3f");
painter->drawLine( rect.topLeft(), rect.topRight());
painter->drawLine( rect.topRight(), rect.bottomRight());
painter->drawLine( rect.bottomRight(), rect.bottomLeft());
painter->drawLine( rect.bottomLeft(), rect.topLeft());
*/
//Pending Michel adjust the painting in case we have a user given Width
//allow it to be larger than the auto calculated width in case we want
//to overlap
if ( params()->userWidth() != 0 ) {
int userwidth = 0;
if ( params()->userWidth() < 0 )
userwidth = static_cast <int> (params()->userWidth() * -areaWidthP1000);
else
userwidth = static_cast <int> (params()->userWidth() * areaWidthP1000);
//if ( userwidth < frontBarWidth ) {
rect.setLeft( static_cast <int> (rect.center().x() - (userwidth/2)));
rect.setRight( static_cast <int> (rect.center().x() + (userwidth/2)));
rect.setWidth( static_cast <int> (userwidth) );
//adjust the front rect
frontRectPositive.setLeft( tooHigh ? static_cast <int> (excessRectPositive.center().x() - (userwidth/2)):rect.left());
frontRectPositive.setRight( tooHigh ? static_cast <int> (excessRectPositive.center().x() + (userwidth/2)):rect.right());
}
// drawing the front side
if (!tooHigh && !tooLow || params()->barChartSubType() == KDChartParams::BarPercent ) {
if ( bMultiRows )
painter->setClipping( false );
else
painter->setClipping( true );
painter->drawRect( rect );
}
// Don't use rect for drawing after this
if ( pointArrayList ) {
rect.moveBy( _dataRect.x(), _dataRect.y());
pointArrayList->append( rectToPointArray( rect ) );
}
}
} // bShowThisBar
} // positive values
}
if ( bShowThisBar && _bThreeDBars && !bDrawExtraLines ) {
//Pending Michel: no need to use that anymore
//const int maxY = 2*devRect.height();
QPointArray points( 4 );
if (cellValue <= 0 || cellValue < minColumnValue) {
if ( tooLow || cellValue < minColumnValue ) {
points.setPoint(0, excessRectNegative.topRight());
points.setPoint(1, excessRectNegative.topRight().x() + static_cast<int>(sideBarHeight),
excessRectNegative.top() - static_cast<int>(sideBarHeight));
points.setPoint(2, excessRectNegative.bottomRight().x() + static_cast<int>(sideBarHeight),
excessRectNegative.bottom() - static_cast<int>(sideBarHeight));
points.setPoint(3, excessRectNegative.bottomRight());
} else {
points.setPoint( 0, frontRectNegative.bottomRight());
points.setPoint( 1, frontRectNegative.bottomRight().x() + static_cast<int>(sideBarHeight),
frontRectNegative.bottom() - static_cast<int>(sideBarHeight) );
points.setPoint(2, frontRectNegative.bottomRight().x() + static_cast<int>(sideBarHeight),
frontRectNegative.top() - static_cast<int>(sideBarHeight));
points.setPoint(3, frontRectNegative.topRight() );
}
rightRectNegative.setTopLeft( points.point(0));
rightRectNegative.setTopRight( points.point(2));
rightRectNegative.setBottomRight(points.point(1));
rightRectNegative.setBottomLeft(points.point(3));
} else {
// Pending Michel
// Make sure to align the right side top and bottom points
// to the front side points
if ( tooHigh ) {
points.setPoint(0, excessRectPositive.topRight());
points.setPoint(1, excessRectPositive.topRight().x() + static_cast <int> (sideBarHeight),
excessRectPositive.top() - static_cast <int> (sideBarHeight) );
points.setPoint(2, excessRectPositive.bottomRight().x() + static_cast <int> (sideBarHeight),
excessRectPositive.bottom() - static_cast <int> (sideBarHeight));
points.setPoint(3, excessRectPositive.bottomRight());
} else {
points.setPoint(0, frontRectPositive.topRight());
points.setPoint(1, frontRectPositive.topRight().x() + static_cast <int> (sideBarHeight),
frontRectPositive.top() - static_cast<int>(sideBarHeight));
points.setPoint(2, frontRectPositive.bottomRight().x() + static_cast <int> (sideBarHeight),
frontRectPositive.bottom() - static_cast<int>(sideBarHeight));
points.setPoint(3, frontRectPositive.bottomRight());
}
//register the right rect
rightRectPositive.setTopLeft( points.point(0));
rightRectPositive.setTopRight( points.point(1));
rightRectPositive.setBottomLeft( points.point(3));
rightRectPositive.setBottomRight(points.point(2));
}
if ( myShadow2Color.isValid() )
painter->setBrush( QBrush( myShadow2Color, params()->shadowPattern() ) );
else
painter->setBrush( NoBrush );
//debug points and lines
/*
painter->drawText( points.point(0), "0r");
painter->drawText( points.point(1), "1r");
painter->drawText( points.point(2), "2r");
painter->drawText( points.point(3), "3r");
painter->drawLine( points.point(0), points.point(1));
painter->drawLine( points.point(1), points.point(2));
painter->drawLine( points.point(2), points.point(3));
painter->drawLine( points.point(3), points.point(0));
*/
//drawing the right side
if( (!tooHigh && !tooLow) || (tooHigh && cellValue <= 0 ) ) {
if (( cellValue != 0 && params()->barChartSubType() == KDChartParams::BarPercent ) ||
( cellValue != 0 && params()->barChartSubType() == KDChartParams::BarStacked ) ||
( cellValue != 0 && bNormalMode ||
cellValue != 0 && bMultiRows)) {
painter->setClipping( false );
painter->drawPolygon( points );
}
}
// Dont override the region stored in case of excess values or barPercent this in order to display
// the value labels closer to the corner of the front bar.
if ( pointArrayList && !tooHigh && !tooLow && params()->barChartSubType() != KDChartParams::BarPercent ) {
QPointArray points2cpy( points.copy() );
//qDebug("g2");
points2cpy.translate( _dataRect.x(), _dataRect.y());
//qDebug("g3");
pointArrayList->append( points2cpy );
//qDebug("g4");
}
// drawing the top, but only for the topmost piece for stacked and percent
if ( bNormalMode || bMultiRows ||
// For stacked and percent bars, there are three ways to determine
// the top:
// 1. all values are negative: the top is the one in the first dataset
( maxValueInThisColumn <= 0.0 && dataset == firstValidValue ) ||
// 2. all values are positive: the top is the one in the last dataset
( minValueInThisColumn >= 0.0 && dataset == lastValidPositiveValue ) ||
// 3. some values are positive, some negative:
// the top is the one in the last positive
// dataset value
( dataset == lastValidPositiveValue ) ) {
if (cellValue <= 0 || cellValue < minColumnValue) {
if ( tooLow ) {
points.setPoint(0,excessRectNegative.bottomLeft());
points.setPoint(1,excessRectNegative.topLeft().x() + static_cast <int> (sideBarHeight),
excessRectNegative.bottom() - static_cast <int> (sideBarHeight));
points.setPoint(2,excessRectNegative.bottomRight().x() + static_cast <int> (sideBarHeight),
excessRectNegative.bottom() - static_cast <int> (sideBarHeight));
points.setPoint(3,excessRectNegative.bottomRight());
}else {
// Align the top to the front and the right side
points.setPoint(0,frontRectNegative.topLeft() );
points.setPoint(1,frontRectNegative.topLeft().x() + static_cast <int> (sideBarHeight), rightRectNegative.top());
points.setPoint(2,rightRectNegative.topRight() );
points.setPoint(3,rightRectNegative.topRight().x() - static_cast <int> (sideBarHeight),
frontRectNegative.topRight().y() );
//make sure the top rect color is the right one - barStacked - Noll values
if ( (params()->barChartSubType() == KDChartParams::BarStacked && cellValue == 0 && maxValueInThisColumn != 0 ) )
points.translate(0, maxValueInThisColumn <= 0?-(static_cast <int> (1*pixelsPerUnit) + 1):
-static_cast<int>(maxValueInThisColumn*pixelsPerUnit));
if ( params()->barChartSubType() == KDChartParams::BarPercent && cellValue == 0 ) {
if ( dataset == datasetEnd && maxValueInThisColumn != 0)
points.translate(0, -static_cast<int>( logHeight - sideBarHeight ));
else if ( maxValueInThisColumn == 0)
points.translate(0, static_cast <int> (logHeight + (sideBarHeight - sideBarWidth)));
}
}
} else {
if ( tooHigh ) {
points.setPoint(0, excessRectPositive.topLeft());
points.setPoint(1, excessRectPositive.topLeft().x() + static_cast <int> (sideBarHeight),
excessRectPositive.top() - static_cast <int> (sideBarHeight) );
points.setPoint(2, excessRectPositive.topRight().x() + static_cast <int> (sideBarHeight),
excessRectPositive.top() - static_cast <int> (sideBarHeight));
points.setPoint(3, excessRectPositive.topRight());
} else {
// Pending Michel
// Align the top to the front and the right side
points.setPoint(0, frontRectPositive.topLeft());
points.setPoint(1, frontRectPositive.topLeft().x() + static_cast <int> (sideBarHeight),
rightRectPositive.top() );
points.setPoint(2, rightRectPositive.topRight());
points.setPoint(3, rightRectPositive.topRight().x() - static_cast <int> (sideBarHeight),
frontRectPositive.topRight().y());
}
}
if (cellValue < 0.0 && maxValueInThisColumn < 0)
painter->setBrush( bMultiRows ? myBarColor : black );
else
painter->setBrush( QBrush( myShadow1Color, params()->shadowPattern() ) );
if ( !myShadow1Color.isValid() )
painter->setBrush( NoBrush ); // override prev. setting
// debug points and lines
/*
painter->drawText( points.point(0), "0t");
painter->drawText( points.point(1), "1t");
painter->drawText( points.point(2), "2t");
painter->drawText( points.point(3), "3t");
painter->drawLine( points.point(0), points.point(1) );
painter->drawLine( points.point(1),points.point(2) );
painter->drawLine( points.point(2),points.point(3) );
painter->drawLine( points.point(3),points.point(0) );
*/
// drawing the top side
if (!tooHigh && !tooLow || (tooHigh && cellValue <= 0) )
painter->drawPolygon( points );
// dont override the region stored in case of excess values.
if (pointArrayList && !tooHigh && !tooLow
&& params()->barChartSubType() != KDChartParams::BarPercent
&& params()->barChartSubType() != KDChartParams::BarStacked) {
points.translate( _dataRect.x(), _dataRect.y());
pointArrayList->append( points );
}
}
}//if ( _bThreeDBars )
if( regions && pointArrayList && ! pointArrayList->empty() ) {
if( bShowThisBar && !bDrawExtraLines ){
KDChartDataRegion * region;
if( _bThreeDBars ){
region = new KDChartDataRegion( dataset, value, chart,
pointArrayList, true );
} else {
// just store a rectangle if NOT in 3-D bar mode
region = new KDChartDataRegion( dataset, value, chart,
pointArrayList->first().boundingRect() );
delete pointArrayList;
}
/*qDebug("KDChartDataRegion stored! x: %i y: %i w: %i h: %i",
region->rect().x(),region->rect().y(),
region->rect().width(),region->rect().height());*/
regions->append( region );
} else {
delete pointArrayList;
}
}
}// if( !bDrawExtraLines || bCellPropertiesFound )
}// if( !skipMe )
}else{
// Do not paint a bar, but update the position
// variable: to find the exact x1 position for
// the next bar that will be drawn.
int iDummy1, iDummy2;
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
iDummy1, iDummy2, prevFrontX2 );
}// if( dataset >= datasetStart && dataset <= datasetEnd )
// Vertical advancement in stacked or percent only if there was a value
// Pending Michel add sideBarHeight in case we are in 3D mode but not for Percent
// where we need to display the top rect but cant resize the YAxis.
if ( params()->barChartSubType() == KDChartParams::BarStacked ||
params()->barChartSubType() == KDChartParams::BarPercent )
if ( /*barHeight*/ cellValue < 0 )
(_bThreeDBars && params()->barChartSubType() != KDChartParams::BarPercent)?yposNegatives -= sideBarHeight + barHeight:
yposNegatives -= barHeight;
else
(_bThreeDBars && params()->barChartSubType() != KDChartParams::BarPercent)?yposPositives -= sideBarHeight + barHeight:
yposPositives -= barHeight;
} else {
// Do not paint a bar, but update the position
// variable: to find the exact x1 position for
// the next bar that will be drawn.
int iDummy1, iDummy2;
calculateXFront1_2( bNormalMode, bIsVeryFirstBar, bIsFirstDataset, _bThreeDBars,
xpos, valueBlockGap, datasetGap, frontBarWidth,
iDummy1, iDummy2, prevFrontX2 );
}
// advance only if the next dataset has DataEntry mode
bool bAdvanceToNextValue =
( bMultiRows ? (dataset == chartDatasetStart) : (dataset == chartDatasetEnd)
|| ( params()->chartSourceMode( bMultiRows ? dataset-1 : dataset+1 )
== KDChartParams::DataEntry ) );
// Advance to next value; only for normal bars
if ( bNormalMode ) {
if( bAdvanceToNextValue )
xpos += barWidth;
// skip gap between datasets, unless last dataset
if ( dataset < myLastDataEntryDataset )
xpos += datasetGap;
}
if( bAdvanceToNextValue || bMultiRows ){
//qDebug("shiftMyPainter( -nShiftX, nShiftY );");
shiftMyPainter( -nShiftX, nShiftY );
}
bIsVeryFirstBar = false;
bIsFirstDataset = false;
}
// Advancement between value blocks
if ( bNormalMode ){
// skip gap between value blocks, don't worry about last one here
xpos += valueBlockGap;
//qDebug("**************************** xpos: %f + valueBlockGap: %f = %f", xpos, valueBlockGap, xpos+valueBlockGap);
}else{
// skip gap between value blocks
xpos += valueBlockGap + barWidth;
//qDebug ( "2/ barWidth %f", barWidth );
//qDebug ( " valueBlockGap %f", valueBlockGap );
// start at bottom with next value group
yposPositives = yposPositivesStart;
yposNegatives = yposNegativesStart;
}
//qDebug("shiftMyPainterBack");
shiftMyPainterBack();
}
}
if( bMultiRows )
painter->setClipping( bHadClipping );
}