chart2/source/view/charttypes/BarChart.cxx | 850 +++++++++++------------ chart2/source/view/charttypes/BarChart.hxx | 47 + chart2/source/view/charttypes/VSeriesPlotter.cxx | 2 chart2/source/view/inc/VSeriesPlotter.hxx | 2 4 files changed, 477 insertions(+), 424 deletions(-)
New commits: commit b0c68f982dfabd33b6049c3614734401f70bae50 Author: Noel Grandin <noel.gran...@collabora.co.uk> AuthorDate: Wed Jan 12 14:21:58 2022 +0200 Commit: Noel Grandin <noel.gran...@collabora.co.uk> CommitDate: Thu Jan 13 07:36:24 2022 +0100 split the BarChart rendering method down a little into slightly more manageable chunks Change-Id: I2a8c031618516102e2dccf071beef51fe9917f82 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/128347 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> diff --git a/chart2/source/view/charttypes/BarChart.cxx b/chart2/source/view/charttypes/BarChart.cxx index 57dc851e2035..06e041e800df 100644 --- a/chart2/source/view/charttypes/BarChart.cxx +++ b/chart2/source/view/charttypes/BarChart.cxx @@ -385,30 +385,6 @@ void BarChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot ); } -namespace { - -//better performance for big data -struct FormerBarPoint -{ - FormerBarPoint( double fX, double fUpperY, double fLowerY, double fZ ) - : m_fX(fX), m_fUpperY(fUpperY), m_fLowerY(fLowerY), m_fZ(fZ) - {} - FormerBarPoint() - : m_fX(std::numeric_limits<double>::quiet_NaN()) - , m_fUpperY(std::numeric_limits<double>::quiet_NaN()) - , m_fLowerY(std::numeric_limits<double>::quiet_NaN()) - , m_fZ(std::numeric_limits<double>::quiet_NaN()) - { - } - - double m_fX; - double m_fUpperY; - double m_fLowerY; - double m_fZ; -}; - -} - void BarChart::adaptOverlapAndGapwidthForGroupBarsPerAxis() { //adapt m_aOverlapSequence and m_aGapwidthSequence for the groupBarsPerAxis feature @@ -465,11 +441,8 @@ void BarChart::createShapes() ShapeFactory::createGroup2D( m_xFinalTarget ); //check necessary here that different Y axis can not be stacked in the same group? ... hm? - double fLogicZ = 1.0;//as defined - bool bDrawConnectionLines = false; bool bDrawConnectionLinesInited = false; - bool bOnlyConnectionLinesForThisPoint = false; std::unordered_set<rtl::Reference<SvxShape>> aShapeSet; @@ -497,8 +470,6 @@ void BarChart::createShapes() //better performance for big data std::map< VDataSeries*, FormerBarPoint > aSeriesFormerPointMap; m_bPointsWereSkipped = false; - sal_Int32 nSkippedPoints = 0; - sal_Int32 nCreatedPoints = 0; sal_Int32 nStartIndex = 0; sal_Int32 nEndIndex = VSeriesPlotter::getPointCount(); @@ -532,395 +503,9 @@ void BarChart::createShapes() sal_Int32 nZ=1; for( auto& rZSlot : m_aZSlots ) { - //iterate through all x slots in this category - double fSlotX=0; - for( auto& rXSlot : rZSlot ) - { - sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries(); - //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot - BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) ); - if(!pPosHelper) - pPosHelper = m_pMainPosHelper.get(); - - PlotterBase::m_pPosHelper = pPosHelper; - - //update/create information for current group - pPosHelper->updateSeriesCount( rZSlot.size() ); - double fLogicBaseWidth = pPosHelper->getScaledSlotWidth(); - - // get distance from base value to maximum and minimum - - double fMinimumY = 0.0, fMaximumY = 0.0; - if( nPointIndex < rXSlot.getPointCount()) - rXSlot.calculateYMinAndMaxForCategory( nPointIndex - , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex ); - - double fLogicPositiveYSum = 0.0; - if( !std::isnan( fMaximumY ) ) - fLogicPositiveYSum = fMaximumY; - - double fLogicNegativeYSum = 0.0; - if( !std::isnan( fMinimumY ) ) - fLogicNegativeYSum = fMinimumY; - - if( pPosHelper->isPercentY() ) - { - /* #i70395# fLogicPositiveYSum contains sum of all positive - values, if any, otherwise the highest negative value. - fLogicNegativeYSum contains sum of all negative values, - if any, otherwise the lowest positive value. - Afterwards, fLogicPositiveYSum will contain the maximum - (positive) value that is related to 100%. */ - - // do nothing if there are positive values only - if( fLogicNegativeYSum < 0.0 ) - { - // fLogicPositiveYSum<0 => negative values only, use absolute of negative sum - if( fLogicPositiveYSum < 0.0 ) - fLogicPositiveYSum = -fLogicNegativeYSum; - // otherwise there are positive and negative values, calculate total distance - else - fLogicPositiveYSum -= fLogicNegativeYSum; - } - fLogicNegativeYSum = 0.0; - } - - double fBaseValue = 0.0; - if( !pPosHelper->isPercentY() && rXSlot.m_aSeriesVector.size()<=1 ) - fBaseValue = pPosHelper->getBaseValueY(); - double fPositiveLogicYForNextSeries = fBaseValue; - double fNegativeLogicYForNextSeries = fBaseValue; - - //iterate through all series in this x slot - for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector ) - { - if(!pSeries) - continue; - - bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor"); - - bOnlyConnectionLinesForThisPoint = false; - - if(nPointIndex==nStartIndex)//do not create a regression line for each point - createRegressionCurvesShapes( *pSeries, xRegressionCurveTarget, xRegressionCurveEquationTarget, - m_pPosHelper->maySkipPointsInRegressionCalculation()); - - if( !bDrawConnectionLinesInited ) - { - bDrawConnectionLines = pSeries->getConnectBars(); - if( m_nDimension==3 ) - bDrawConnectionLines = false; - if( bDrawConnectionLines && rXSlot.m_aSeriesVector.size()==1 ) - { - //detect whether we have a stacked chart or not: - StackingDirection eDirection = pSeries->getStackingDirection(); - if( eDirection != StackingDirection_Y_STACKING ) - bDrawConnectionLines = false; - } - bDrawConnectionLinesInited = true; - } - - // Use another XShapes for background, so we can avoid needing to set the Z-order on all of them, - // which is expensive in bulk. - rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget)); - rtl::Reference<SvxShapeGroupAnyD> xSeriesBackgroundShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget)); - aShapeSet.insert(xSeriesGroupShape_Shapes); - aShapeSet.insert(xSeriesBackgroundShape_Shapes); - // Suspend setting rects dirty for the duration of this call - E3dScene* pScene = dynamic_cast<E3dScene*>(xSeriesGroupShape_Shapes->GetSdrObject()); - if (pScene) - pScene->SuspendReportingDirtyRects(); - pScene = dynamic_cast<E3dScene*>(xSeriesBackgroundShape_Shapes->GetSdrObject()); - if (pScene) - pScene->SuspendReportingDirtyRects(); - - //collect data point information (logic coordinates, style ): - double fUnscaledLogicX = pSeries->getXValue( nPointIndex ); - fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution ); - if(std::isnan(fUnscaledLogicX)) - continue;//point not visible - if(fUnscaledLogicX<pPosHelper->getLogicMinX()) - continue;//point not visible - if(fUnscaledLogicX>pPosHelper->getLogicMaxX()) - continue;//point not visible - if(pPosHelper->isStrongLowerRequested(0) && fUnscaledLogicX==pPosHelper->getLogicMaxX()) - continue;//point not visible - double fLogicX = pPosHelper->getScaledSlotPos( fUnscaledLogicX, fSlotX ); - - double fLogicBarHeight = pSeries->getYValue( nPointIndex ); - if( std::isnan( fLogicBarHeight )) //no value at this category - continue; - - double fLogicValueForLabeDisplay = fLogicBarHeight; - fLogicBarHeight-=fBaseValue; - - if( pPosHelper->isPercentY() ) - { - if(fLogicPositiveYSum!=0.0) - fLogicBarHeight = fabs( fLogicBarHeight )/fLogicPositiveYSum; - else - fLogicBarHeight = 0.0; - } - - // tdf#114141 to draw the top of the zero height 3D bar - // we set a small positive value, here the smallest one for the type double (DBL_MIN) - if( fLogicBarHeight == 0.0 ) - fLogicBarHeight = DBL_MIN; - - //sort negative and positive values, to display them on different sides of the x axis - bool bPositive = fLogicBarHeight >= 0.0; - double fLowerYValue = bPositive ? fPositiveLogicYForNextSeries : fNegativeLogicYForNextSeries; - double fUpperYValue = fLowerYValue+fLogicBarHeight; - if( bPositive ) - fPositiveLogicYForNextSeries += fLogicBarHeight; - else - fNegativeLogicYForNextSeries += fLogicBarHeight; - - if(m_nDimension==3) - fLogicZ = nZ+0.5; - - drawing::Position3D aUnscaledLogicPosition( fUnscaledLogicX, fUpperYValue, fLogicZ ); - - //@todo ... start an iteration over the different breaks of the axis - //each subsystem may add an additional shape to form the whole point - //create a group shape for this point and add to the series shape: - // uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( createGroupShape(xSeriesGroupShape_Shapes) ); - // uno::Reference<drawing::XShape> xPointGroupShape_Shape = - // uno::Reference<drawing::XShape>( xPointGroupShape_Shapes, uno::UNO_QUERY ); - //as long as we do not iterate we do not need to create an additional group for each point - uno::Reference< beans::XPropertySet > xDataPointProperties( pSeries->getPropertiesOfPoint( nPointIndex ) ); - sal_Int32 nGeometry3D = DataPointGeometry3D::CUBOID; - if(m_nDimension==3) try - { - xDataPointProperties->getPropertyValue( "Geometry3D") >>= nGeometry3D; - } - catch( const uno::Exception& ) - { - TOOLS_WARN_EXCEPTION("chart2", "" ); - } - - //@todo iterate through all subsystems to create partial points - { - //@todo select a suitable PositionHelper for this subsystem - BarPositionHelper* pSubPosHelper = pPosHelper; - - double fUnclippedUpperYValue = fUpperYValue; - - //apply clipping to Y - if( !pPosHelper->clipYRange(fLowerYValue,fUpperYValue) ) - { - if( bDrawConnectionLines ) - bOnlyConnectionLinesForThisPoint = true; - else - continue; - } - //@todo clipping of X and Z is not fully integrated so far, as there is a need to create different objects - - //apply scaling to Y before calculating width (necessary to maintain gradient in clipped objects) - pSubPosHelper->doLogicScaling(nullptr,&fLowerYValue,nullptr); - pSubPosHelper->doLogicScaling(nullptr,&fUpperYValue,nullptr); - //scaling of X and Z is not provided as the created objects should be symmetric in that dimensions - - pSubPosHelper->doLogicScaling(nullptr,&fUnclippedUpperYValue,nullptr); - - //calculate resulting width - double fCompleteHeight = bPositive ? fLogicPositiveYSum : fLogicNegativeYSum; - if( pPosHelper->isPercentY() ) - fCompleteHeight = 1.0; - double fLogicBarWidth = fLogicBaseWidth; - double fTopHeight=approxSub(fCompleteHeight,fUpperYValue); - if(!bPositive) - fTopHeight=approxSub(fCompleteHeight,fLowerYValue); - double fLogicYStart = bPositive ? fLowerYValue : fUpperYValue; - double fMiddleHeight = fUpperYValue-fLowerYValue; - if(!bPositive) - fMiddleHeight*=-1.0; - double fLogicBarDepth = 0.5; - if(m_nDimension==3) - { - if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 ) - { - double fHeight = fCompleteHeight-fLowerYValue; - if(!bPositive) - fHeight = fCompleteHeight-fUpperYValue; - fLogicBarWidth = fLogicBaseWidth*fHeight/fCompleteHeight; - if(fLogicBarWidth<=0.0) - fLogicBarWidth=fLogicBaseWidth; - fLogicBarDepth = fLogicBarDepth*fHeight/fCompleteHeight; - if(fLogicBarDepth<=0.0) - fLogicBarDepth*=-1.0; - } - } - - //better performance for big data - FormerBarPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] ); - pPosHelper->setCoordinateSystemResolution( m_aCoordinateSystemResolution ); - if( !pSeries->isAttributedDataPoint(nPointIndex) - && - pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fUpperY, aFormerPoint.m_fZ - , fLogicX, fUpperYValue, fLogicZ ) - && - pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fLowerY, aFormerPoint.m_fZ - , fLogicX, fLowerYValue, fLogicZ ) - ) - { - nSkippedPoints++; - m_bPointsWereSkipped = true; - continue; - } - aSeriesFormerPointMap[pSeries.get()] = FormerBarPoint(fLogicX,fUpperYValue,fLowerYValue,fLogicZ); - - if( bDrawConnectionLines ) - { - //store point information for connection lines - - drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ ); - drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ ); - - if( isValidPosition(aLeftUpperPoint) ) - AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aLeftUpperPoint ); - if( isValidPosition(aRightUpperPoint) ) - AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aRightUpperPoint ); - } - - if( bOnlyConnectionLinesForThisPoint ) - continue; - - //maybe additional possibility for performance improvement - //bool bCreateLineInsteadOfComplexGeometryDueToMissingSpace = false; - //pPosHelper->isSameForGivenResolution( fLogicX-fLogicBarWidth/2.0, fLowerYValue, fLogicZ - // , fLogicX+fLogicBarWidth/2.0, fLowerYValue, fLogicZ ); - - nCreatedPoints++; - //create partial point - if( !approxEqual(fLowerYValue,fUpperYValue) ) - { - if( m_nDimension==3 ) - { - drawing::Position3D aLogicBottom (fLogicX,fLogicYStart,fLogicZ); - drawing::Position3D aLogicLeftBottomFront (fLogicX+fLogicBarWidth/2.0,fLogicYStart,fLogicZ-fLogicBarDepth/2.0); - drawing::Position3D aLogicRightDeepTop (fLogicX-fLogicBarWidth/2.0,fLogicYStart+fMiddleHeight,fLogicZ+fLogicBarDepth/2.0); - drawing::Position3D aLogicTopTop (fLogicX,fLogicYStart+fMiddleHeight+fTopHeight,fLogicZ); - - ::chart::XTransformation2* pTransformation = pSubPosHelper->getTransformationScaledLogicToScene(); - - //transformation 3) -> 4) - drawing::Position3D aTransformedBottom ( pTransformation->transform( aLogicBottom ) ); - drawing::Position3D aTransformedLeftBottomFront ( pTransformation->transform( aLogicLeftBottomFront ) ); - drawing::Position3D aTransformedRightDeepTop ( pTransformation->transform( aLogicRightDeepTop ) ); - drawing::Position3D aTransformedTopTop ( pTransformation->transform( aLogicTopTop ) ); - - drawing::Direction3D aSize = aTransformedRightDeepTop - aTransformedLeftBottomFront; - drawing::Direction3D aTopSize( aTransformedTopTop - aTransformedRightDeepTop ); - fTopHeight = aTopSize.DirectionY; - - sal_Int32 nRotateZAngleHundredthDegree = 0; - if( pPosHelper->isSwapXAndY() ) - { - fTopHeight = aTopSize.DirectionX; - nRotateZAngleHundredthDegree = 90*100; - aSize = drawing::Direction3D(aSize.DirectionY,aSize.DirectionX,aSize.DirectionZ); - } - - if( aSize.DirectionX < 0 ) - aSize.DirectionX *= -1.0; - if( aSize.DirectionZ < 0 ) - aSize.DirectionZ *= -1.0; - if( fTopHeight < 0 ) - fTopHeight *= -1.0; - - rtl::Reference< SvxShape > xShape = createDataPoint3D_Bar( - xSeriesGroupShape_Shapes, aTransformedBottom, aSize, fTopHeight, nRotateZAngleHundredthDegree - , xDataPointProperties, nGeometry3D ); - - if(bHasFillColorMapping) - { - double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); - if(!std::isnan(nPropVal)) - { - xShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal))); - } - } - //set name/classified ObjectID (CID) - ShapeFactory::setShapeName(xShape - , ObjectIdentifier::createPointCID( - pSeries->getPointCID_Stub(),nPointIndex) ); - } - else //m_nDimension!=3 - { - // performance improvement: alloc the sequence before the rendering - // otherwise we have 2 realloc calls - std::vector<std::vector<css::drawing::Position3D>> aPoly; - aPoly.resize(1); - drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUpperYValue,fLogicZ ); - drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUpperYValue,fLogicZ ); - - AddPointToPoly( aPoly, drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); - AddPointToPoly( aPoly, drawing::Position3D( fLogicX+fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); - AddPointToPoly( aPoly, aRightUpperPoint ); - AddPointToPoly( aPoly, aLeftUpperPoint ); - AddPointToPoly( aPoly, drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); - pPosHelper->transformScaledLogicToScene( aPoly ); - std::optional<sal_Int32> xFillColor; - if(bHasFillColorMapping) - { - double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); - if(!std::isnan(nPropVal)) - xFillColor = static_cast<sal_Int32>(nPropVal); - } - SdrPathObj* pShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes, aPoly, /*bSetZOrderToZero*/false ); - PropertyMapper::setPropertyNameMapForFilledSeriesProperties(pShape, xDataPointProperties, xFillColor); - - //set name/classified ObjectID (CID) - ShapeFactory::setShapeName(pShape - , ObjectIdentifier::createPointCID( - pSeries->getPointCID_Stub(),nPointIndex) ); - } - - } - - //create error bar - createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nPointIndex, m_xLogicTarget, &fLogicX ); - - //create data point label - if( pSeries->getDataPointLabelIfLabel(nPointIndex) ) - { - double fLogicSum = aLogicYSumMap[nAttachedAxisIndex]; - - LabelAlignment eAlignment(LABEL_ALIGN_CENTER); - sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, pPosHelper->isSwapXAndY() ); - - double fLowerBarDepth = fLogicBarDepth; - double fUpperBarDepth = fLogicBarDepth; - { - if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 ) - { - double fOuterBarDepth = fLogicBarDepth * fTopHeight/(fabs(fCompleteHeight)); - fLowerBarDepth = (fBaseValue < fUpperYValue) ? fabs(fLogicBarDepth) : fabs(fOuterBarDepth); - fUpperBarDepth = (fBaseValue < fUpperYValue) ? fabs(fOuterBarDepth) : fabs(fLogicBarDepth); - } - } - - awt::Point aScreenPosition2D = getLabelScreenPositionAndAlignment( - eAlignment, nLabelPlacement, fLogicX, fLowerYValue, fUpperYValue, fLogicZ, - fLowerBarDepth, fUpperBarDepth, fBaseValue, pPosHelper); - sal_Int32 nOffset = 0; - if(eAlignment!=LABEL_ALIGN_CENTER) - { - nOffset = 100;//add some spacing //@todo maybe get more intelligent values - if( m_nDimension == 3 ) - nOffset = 260; - } - createDataLabel( - xTextTarget, *pSeries, nPointIndex, - fLogicValueForLabeDisplay, fLogicSum, aScreenPosition2D, eAlignment, nOffset); - } - - }//end iteration through partial points - - }//next series in x slot (next y slot) - fSlotX+=1.0; - }//next x slot + doZSlot(bDrawConnectionLines, bDrawConnectionLinesInited, rZSlot, nZ, nPointIndex, nStartIndex, + xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget, + aShapeSet, aSeriesFormerPointMap, aLogicYSumMap); ++nZ; }//next z slot }//next category @@ -973,11 +558,432 @@ void BarChart::createShapes() /* @todo remove series shapes if empty */ +} + +void BarChart::doZSlot( + bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, + const std::vector< VDataSeriesGroup >& rZSlot, + const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex, + rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget, + rtl::Reference<SvxShapeGroupAnyD>& xTextTarget, + std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet, + std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap, + std::map< sal_Int32, double >& aLogicYSumMap) +{ + //iterate through all x slots in this category + double fSlotX=0; + for( auto& rXSlot : rZSlot ) + { + sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries(); + //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot + BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) ); + if(!pPosHelper) + pPosHelper = m_pMainPosHelper.get(); + + PlotterBase::m_pPosHelper = pPosHelper; + + //update/create information for current group + pPosHelper->updateSeriesCount( rZSlot.size() ); + double fLogicBaseWidth = pPosHelper->getScaledSlotWidth(); + + // get distance from base value to maximum and minimum + + double fMinimumY = 0.0, fMaximumY = 0.0; + if( nPointIndex < rXSlot.getPointCount()) + rXSlot.calculateYMinAndMaxForCategory( nPointIndex + , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex ); + + double fLogicPositiveYSum = 0.0; + if( !std::isnan( fMaximumY ) ) + fLogicPositiveYSum = fMaximumY; + + double fLogicNegativeYSum = 0.0; + if( !std::isnan( fMinimumY ) ) + fLogicNegativeYSum = fMinimumY; + + if( pPosHelper->isPercentY() ) + { + /* #i70395# fLogicPositiveYSum contains sum of all positive + values, if any, otherwise the highest negative value. + fLogicNegativeYSum contains sum of all negative values, + if any, otherwise the lowest positive value. + Afterwards, fLogicPositiveYSum will contain the maximum + (positive) value that is related to 100%. */ + + // do nothing if there are positive values only + if( fLogicNegativeYSum < 0.0 ) + { + // fLogicPositiveYSum<0 => negative values only, use absolute of negative sum + if( fLogicPositiveYSum < 0.0 ) + fLogicPositiveYSum = -fLogicNegativeYSum; + // otherwise there are positive and negative values, calculate total distance + else + fLogicPositiveYSum -= fLogicNegativeYSum; + } + fLogicNegativeYSum = 0.0; + } + + doXSlot(rXSlot, bDrawConnectionLines, bDrawConnectionLinesInited, nZ, nPointIndex, nStartIndex, + xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget, + aShapeSet, aSeriesFormerPointMap, aLogicYSumMap, + fLogicBaseWidth, fSlotX, pPosHelper, fLogicPositiveYSum, fLogicNegativeYSum, nAttachedAxisIndex); + + fSlotX+=1.0; + }//next x slot +} + + +void BarChart::doXSlot( + const VDataSeriesGroup& rXSlot, + bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, + const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex, + rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget, + rtl::Reference<SvxShapeGroupAnyD>& xTextTarget, + std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet, + std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap, + std::map< sal_Int32, double >& aLogicYSumMap, + const double fLogicBaseWidth, const double fSlotX, + BarPositionHelper* const pPosHelper, + const double fLogicPositiveYSum, const double fLogicNegativeYSum, + const sal_Int32 nAttachedAxisIndex) +{ + double fBaseValue = 0.0; + if( !pPosHelper->isPercentY() && rXSlot.m_aSeriesVector.size()<=1 ) + fBaseValue = pPosHelper->getBaseValueY(); + double fPositiveLogicYForNextSeries = fBaseValue; + double fNegativeLogicYForNextSeries = fBaseValue; + + //iterate through all series in this x slot + for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector ) + { + if(!pSeries) + continue; + + bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor"); + + bool bOnlyConnectionLinesForThisPoint = false; + + if(nPointIndex==nStartIndex)//do not create a regression line for each point + createRegressionCurvesShapes( *pSeries, xRegressionCurveTarget, xRegressionCurveEquationTarget, + m_pPosHelper->maySkipPointsInRegressionCalculation()); + + if( !bDrawConnectionLinesInited ) + { + bDrawConnectionLines = pSeries->getConnectBars(); + if( m_nDimension==3 ) + bDrawConnectionLines = false; + if( bDrawConnectionLines && rXSlot.m_aSeriesVector.size()==1 ) + { + //detect whether we have a stacked chart or not: + StackingDirection eDirection = pSeries->getStackingDirection(); + if( eDirection != StackingDirection_Y_STACKING ) + bDrawConnectionLines = false; + } + bDrawConnectionLinesInited = true; + } + + // Use another XShapes for background, so we can avoid needing to set the Z-order on all of them, + // which is expensive in bulk. + rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget)); + rtl::Reference<SvxShapeGroupAnyD> xSeriesBackgroundShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget)); + aShapeSet.insert(xSeriesGroupShape_Shapes); + aShapeSet.insert(xSeriesBackgroundShape_Shapes); + // Suspend setting rects dirty for the duration of this call + E3dScene* pScene = dynamic_cast<E3dScene*>(xSeriesGroupShape_Shapes->GetSdrObject()); + if (pScene) + pScene->SuspendReportingDirtyRects(); + pScene = dynamic_cast<E3dScene*>(xSeriesBackgroundShape_Shapes->GetSdrObject()); + if (pScene) + pScene->SuspendReportingDirtyRects(); + + //collect data point information (logic coordinates, style ): + double fUnscaledLogicX = pSeries->getXValue( nPointIndex ); + fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution ); + if(std::isnan(fUnscaledLogicX)) + continue;//point not visible + if(fUnscaledLogicX<pPosHelper->getLogicMinX()) + continue;//point not visible + if(fUnscaledLogicX>pPosHelper->getLogicMaxX()) + continue;//point not visible + if(pPosHelper->isStrongLowerRequested(0) && fUnscaledLogicX==pPosHelper->getLogicMaxX()) + continue;//point not visible + double fLogicX = pPosHelper->getScaledSlotPos( fUnscaledLogicX, fSlotX ); + + double fLogicBarHeight = pSeries->getYValue( nPointIndex ); + if( std::isnan( fLogicBarHeight )) //no value at this category + continue; + + double fLogicValueForLabeDisplay = fLogicBarHeight; + fLogicBarHeight-=fBaseValue; + + if( pPosHelper->isPercentY() ) + { + if(fLogicPositiveYSum!=0.0) + fLogicBarHeight = fabs( fLogicBarHeight )/fLogicPositiveYSum; + else + fLogicBarHeight = 0.0; + } + + // tdf#114141 to draw the top of the zero height 3D bar + // we set a small positive value, here the smallest one for the type double (DBL_MIN) + if( fLogicBarHeight == 0.0 ) + fLogicBarHeight = DBL_MIN; + + //sort negative and positive values, to display them on different sides of the x axis + bool bPositive = fLogicBarHeight >= 0.0; + double fLowerYValue = bPositive ? fPositiveLogicYForNextSeries : fNegativeLogicYForNextSeries; + double fUpperYValue = fLowerYValue+fLogicBarHeight; + if( bPositive ) + fPositiveLogicYForNextSeries += fLogicBarHeight; + else + fNegativeLogicYForNextSeries += fLogicBarHeight; + + double fLogicZ = 1.0;//as defined + if(m_nDimension==3) + fLogicZ = nZ+0.5; + + drawing::Position3D aUnscaledLogicPosition( fUnscaledLogicX, fUpperYValue, fLogicZ ); + + //@todo ... start an iteration over the different breaks of the axis + //each subsystem may add an additional shape to form the whole point + //create a group shape for this point and add to the series shape: +// uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( createGroupShape(xSeriesGroupShape_Shapes) ); +// uno::Reference<drawing::XShape> xPointGroupShape_Shape = +// uno::Reference<drawing::XShape>( xPointGroupShape_Shapes, uno::UNO_QUERY ); + //as long as we do not iterate we do not need to create an additional group for each point + uno::Reference< beans::XPropertySet > xDataPointProperties( pSeries->getPropertiesOfPoint( nPointIndex ) ); + sal_Int32 nGeometry3D = DataPointGeometry3D::CUBOID; + if(m_nDimension==3) try + { + xDataPointProperties->getPropertyValue( "Geometry3D") >>= nGeometry3D; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + + //@todo iterate through all subsystems to create partial points + { + //@todo select a suitable PositionHelper for this subsystem + BarPositionHelper* pSubPosHelper = pPosHelper; + + double fUnclippedUpperYValue = fUpperYValue; + + //apply clipping to Y + if( !pPosHelper->clipYRange(fLowerYValue,fUpperYValue) ) + { + if( bDrawConnectionLines ) + bOnlyConnectionLinesForThisPoint = true; + else + continue; + } + //@todo clipping of X and Z is not fully integrated so far, as there is a need to create different objects + + //apply scaling to Y before calculating width (necessary to maintain gradient in clipped objects) + pSubPosHelper->doLogicScaling(nullptr,&fLowerYValue,nullptr); + pSubPosHelper->doLogicScaling(nullptr,&fUpperYValue,nullptr); + //scaling of X and Z is not provided as the created objects should be symmetric in that dimensions + + pSubPosHelper->doLogicScaling(nullptr,&fUnclippedUpperYValue,nullptr); + + //calculate resulting width + double fCompleteHeight = bPositive ? fLogicPositiveYSum : fLogicNegativeYSum; + if( pPosHelper->isPercentY() ) + fCompleteHeight = 1.0; + double fLogicBarWidth = fLogicBaseWidth; + double fTopHeight=approxSub(fCompleteHeight,fUpperYValue); + if(!bPositive) + fTopHeight=approxSub(fCompleteHeight,fLowerYValue); + double fLogicYStart = bPositive ? fLowerYValue : fUpperYValue; + double fMiddleHeight = fUpperYValue-fLowerYValue; + if(!bPositive) + fMiddleHeight*=-1.0; + double fLogicBarDepth = 0.5; + if(m_nDimension==3) + { + if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 ) + { + double fHeight = fCompleteHeight-fLowerYValue; + if(!bPositive) + fHeight = fCompleteHeight-fUpperYValue; + fLogicBarWidth = fLogicBaseWidth*fHeight/fCompleteHeight; + if(fLogicBarWidth<=0.0) + fLogicBarWidth=fLogicBaseWidth; + fLogicBarDepth = fLogicBarDepth*fHeight/fCompleteHeight; + if(fLogicBarDepth<=0.0) + fLogicBarDepth*=-1.0; + } + } + + //better performance for big data + FormerBarPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] ); + pPosHelper->setCoordinateSystemResolution( m_aCoordinateSystemResolution ); + if( !pSeries->isAttributedDataPoint(nPointIndex) + && + pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fUpperY, aFormerPoint.m_fZ + , fLogicX, fUpperYValue, fLogicZ ) + && + pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fLowerY, aFormerPoint.m_fZ + , fLogicX, fLowerYValue, fLogicZ ) + ) + { + m_bPointsWereSkipped = true; + continue; + } + aSeriesFormerPointMap[pSeries.get()] = FormerBarPoint(fLogicX,fUpperYValue,fLowerYValue,fLogicZ); + + if( bDrawConnectionLines ) + { + //store point information for connection lines + + drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ ); + drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ ); + + if( isValidPosition(aLeftUpperPoint) ) + AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aLeftUpperPoint ); + if( isValidPosition(aRightUpperPoint) ) + AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aRightUpperPoint ); + } + + if( bOnlyConnectionLinesForThisPoint ) + continue; + + //maybe additional possibility for performance improvement + //bool bCreateLineInsteadOfComplexGeometryDueToMissingSpace = false; + //pPosHelper->isSameForGivenResolution( fLogicX-fLogicBarWidth/2.0, fLowerYValue, fLogicZ + // , fLogicX+fLogicBarWidth/2.0, fLowerYValue, fLogicZ ); + + //create partial point + if( !approxEqual(fLowerYValue,fUpperYValue) ) + { + if( m_nDimension==3 ) + { + drawing::Position3D aLogicBottom (fLogicX,fLogicYStart,fLogicZ); + drawing::Position3D aLogicLeftBottomFront (fLogicX+fLogicBarWidth/2.0,fLogicYStart,fLogicZ-fLogicBarDepth/2.0); + drawing::Position3D aLogicRightDeepTop (fLogicX-fLogicBarWidth/2.0,fLogicYStart+fMiddleHeight,fLogicZ+fLogicBarDepth/2.0); + drawing::Position3D aLogicTopTop (fLogicX,fLogicYStart+fMiddleHeight+fTopHeight,fLogicZ); + + ::chart::XTransformation2* pTransformation = pSubPosHelper->getTransformationScaledLogicToScene(); + + //transformation 3) -> 4) + drawing::Position3D aTransformedBottom ( pTransformation->transform( aLogicBottom ) ); + drawing::Position3D aTransformedLeftBottomFront ( pTransformation->transform( aLogicLeftBottomFront ) ); + drawing::Position3D aTransformedRightDeepTop ( pTransformation->transform( aLogicRightDeepTop ) ); + drawing::Position3D aTransformedTopTop ( pTransformation->transform( aLogicTopTop ) ); + + drawing::Direction3D aSize = aTransformedRightDeepTop - aTransformedLeftBottomFront; + drawing::Direction3D aTopSize( aTransformedTopTop - aTransformedRightDeepTop ); + fTopHeight = aTopSize.DirectionY; + + sal_Int32 nRotateZAngleHundredthDegree = 0; + if( pPosHelper->isSwapXAndY() ) + { + fTopHeight = aTopSize.DirectionX; + nRotateZAngleHundredthDegree = 90*100; + aSize = drawing::Direction3D(aSize.DirectionY,aSize.DirectionX,aSize.DirectionZ); + } + + if( aSize.DirectionX < 0 ) + aSize.DirectionX *= -1.0; + if( aSize.DirectionZ < 0 ) + aSize.DirectionZ *= -1.0; + if( fTopHeight < 0 ) + fTopHeight *= -1.0; + + rtl::Reference< SvxShape > xShape = createDataPoint3D_Bar( + xSeriesGroupShape_Shapes, aTransformedBottom, aSize, fTopHeight, nRotateZAngleHundredthDegree + , xDataPointProperties, nGeometry3D ); + + if(bHasFillColorMapping) + { + double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); + if(!std::isnan(nPropVal)) + { + xShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal))); + } + } + //set name/classified ObjectID (CID) + ShapeFactory::setShapeName(xShape + , ObjectIdentifier::createPointCID( + pSeries->getPointCID_Stub(),nPointIndex) ); + } + else //m_nDimension!=3 + { + // performance improvement: alloc the sequence before the rendering + // otherwise we have 2 realloc calls + std::vector<std::vector<css::drawing::Position3D>> aPoly; + aPoly.resize(1); + drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUpperYValue,fLogicZ ); + drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUpperYValue,fLogicZ ); + + AddPointToPoly( aPoly, drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); + AddPointToPoly( aPoly, drawing::Position3D( fLogicX+fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); + AddPointToPoly( aPoly, aRightUpperPoint ); + AddPointToPoly( aPoly, aLeftUpperPoint ); + AddPointToPoly( aPoly, drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ) ); + pPosHelper->transformScaledLogicToScene( aPoly ); + std::optional<sal_Int32> xFillColor; + if(bHasFillColorMapping) + { + double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); + if(!std::isnan(nPropVal)) + xFillColor = static_cast<sal_Int32>(nPropVal); + } + SdrPathObj* pShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes, aPoly, /*bSetZOrderToZero*/false ); + PropertyMapper::setPropertyNameMapForFilledSeriesProperties(pShape, xDataPointProperties, xFillColor); + + //set name/classified ObjectID (CID) + ShapeFactory::setShapeName(pShape + , ObjectIdentifier::createPointCID( + pSeries->getPointCID_Stub(),nPointIndex) ); + } + + } + + //create error bar + createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nPointIndex, m_xLogicTarget, &fLogicX ); + + //create data point label + if( pSeries->getDataPointLabelIfLabel(nPointIndex) ) + { + double fLogicSum = aLogicYSumMap[nAttachedAxisIndex]; + + LabelAlignment eAlignment(LABEL_ALIGN_CENTER); + sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, pPosHelper->isSwapXAndY() ); + + double fLowerBarDepth = fLogicBarDepth; + double fUpperBarDepth = fLogicBarDepth; + { + if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 ) + { + double fOuterBarDepth = fLogicBarDepth * fTopHeight/(fabs(fCompleteHeight)); + fLowerBarDepth = (fBaseValue < fUpperYValue) ? fabs(fLogicBarDepth) : fabs(fOuterBarDepth); + fUpperBarDepth = (fBaseValue < fUpperYValue) ? fabs(fOuterBarDepth) : fabs(fLogicBarDepth); + } + } + + awt::Point aScreenPosition2D = getLabelScreenPositionAndAlignment( + eAlignment, nLabelPlacement, fLogicX, fLowerYValue, fUpperYValue, fLogicZ, + fLowerBarDepth, fUpperBarDepth, fBaseValue, pPosHelper); + sal_Int32 nOffset = 0; + if(eAlignment!=LABEL_ALIGN_CENTER) + { + nOffset = 100;//add some spacing //@todo maybe get more intelligent values + if( m_nDimension == 3 ) + nOffset = 260; + } + createDataLabel( + xTextTarget, *pSeries, nPointIndex, + fLogicValueForLabeDisplay, fLogicSum, aScreenPosition2D, eAlignment, nOffset); + } + + }//end iteration through partial points - SAL_INFO( - "chart2", - "skipped points: " << nSkippedPoints << " created points: " - << nCreatedPoints); + }//next series in x slot (next y slot) } } //namespace chart diff --git a/chart2/source/view/charttypes/BarChart.hxx b/chart2/source/view/charttypes/BarChart.hxx index 08eaeadab2e6..5e9ab5e49f48 100644 --- a/chart2/source/view/charttypes/BarChart.hxx +++ b/chart2/source/view/charttypes/BarChart.hxx @@ -61,6 +61,53 @@ private: //methods void adaptOverlapAndGapwidthForGroupBarsPerAxis(); + //better performance for big data + struct FormerBarPoint + { + FormerBarPoint( double fX, double fUpperY, double fLowerY, double fZ ) + : m_fX(fX), m_fUpperY(fUpperY), m_fLowerY(fLowerY), m_fZ(fZ) + {} + FormerBarPoint() + : m_fX(std::numeric_limits<double>::quiet_NaN()) + , m_fUpperY(std::numeric_limits<double>::quiet_NaN()) + , m_fLowerY(std::numeric_limits<double>::quiet_NaN()) + , m_fZ(std::numeric_limits<double>::quiet_NaN()) + { + } + + double m_fX; + double m_fUpperY; + double m_fLowerY; + double m_fZ; + }; + + void doZSlot( + bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, const std::vector< VDataSeriesGroup >& rZSlot, + sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex, + rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget, + rtl::Reference<SvxShapeGroupAnyD>& xTextTarget, + std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet, + std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap, + std::map< sal_Int32, double >& aLogicYSumMap); + + void doXSlot( + const VDataSeriesGroup& rXSlot, + bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, + sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex, + rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget, + rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget, + rtl::Reference<SvxShapeGroupAnyD>& xTextTarget, + std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet, + std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap, + std::map< sal_Int32, double >& aLogicYSumMap, + double fLogicBaseWidth, double fSlotX, + BarPositionHelper* const pPosHelper, + double fLogicPositiveYSum, double fLogicNegativeYSum, + sal_Int32 nAttachedAxisIndex); + private: //member std::unique_ptr<BarPositionHelper> m_pMainPosHelper; css::uno::Sequence< sal_Int32 > m_aOverlapSequence; diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx index d2ee85a6a100..3cff976ac66f 100644 --- a/chart2/source/view/charttypes/VSeriesPlotter.cxx +++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx @@ -2092,7 +2092,7 @@ void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange( void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex , bool bSeparateStackingForDifferentSigns - , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) + , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const { assert(nCategoryIndex >= 0); assert(nCategoryIndex < getPointCount()); diff --git a/chart2/source/view/inc/VSeriesPlotter.hxx b/chart2/source/view/inc/VSeriesPlotter.hxx index cc92aa969dda..83d97bc8b671 100644 --- a/chart2/source/view/inc/VSeriesPlotter.hxx +++ b/chart2/source/view/inc/VSeriesPlotter.hxx @@ -90,7 +90,7 @@ public: void calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex , bool bSeparateStackingForDifferentSigns - , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ); + , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const; void calculateYMinAndMaxForCategoryRange( sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex , bool bSeparateStackingForDifferentSigns , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex );