From d81c98623b2abb991ca696b3b09011f6bb3de673 Mon Sep 17 00:00:00 2001 From: Kurt Nordback Date: Fri, 1 Sep 2023 16:13:15 -0600 Subject: [PATCH] tdf#50934: Restructuring for of-pie charts Change-Id: I9682c314efb888d57e94f82f084cc6d0825a4408 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160723 Tested-by: Jenkins Reviewed-by: Noel Grandin --- chart2/source/view/charttypes/PieChart.cxx | 308 ++++++++++++--------- chart2/source/view/charttypes/PieChart.hxx | 14 + 2 files changed, 184 insertions(+), 138 deletions(-) diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx index 0f924de7c5ab..49cf48de3f05 100644 --- a/chart2/source/view/charttypes/PieChart.cxx +++ b/chart2/source/view/charttypes/PieChart.cxx @@ -790,13 +790,6 @@ void PieChart::createShapes() ///the angle axis scale range is [0, 1]. The max_offset parameter is used ///for exploded pie chart and its value is 0.5. - ///the `explodeable` ring is the first one except when the radius axis - ///orientation is reversed (always!?) and we are dealing with a donut: in - ///such a case the `explodeable` ring is the last one. - std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0; - if( m_aPosHelper.isMathematicalOrientationRadius() && m_bUseRings ) - nExplodeableSlot = m_aZSlots.front().size()-1; - m_aLabelInfoList.clear(); m_fMaxOffset = std::numeric_limits::quiet_NaN(); sal_Int32 n3DRelativeHeight = 100; @@ -815,8 +808,6 @@ void PieChart::createShapes() ///(m_bUseRings||fSlotX<0.5) for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 ) { - ShapeParam aParam; - std::vector< std::unique_ptr >* pSeriesList = &(aXSlotIter->m_aSeriesVector); if(pSeriesList->empty())//there should be only one series in each x slot continue; @@ -824,8 +815,6 @@ void PieChart::createShapes() if(!pSeries) continue; - bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor"); - /// The angle degree offset is set by the same property of the /// data series. /// Counter-clockwise offset from the 3 o'clock position. @@ -835,6 +824,8 @@ void PieChart::createShapes() ///the current data series sal_Int32 nPointIndex=0; sal_Int32 nPointCount=pSeries->getTotalPointCount(); + ShapeParam aParam; + for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ ) { double fY = pSeries->getYValue( nPointIndex ); @@ -851,136 +842,177 @@ void PieChart::createShapes() // Total sum of all Y values in this series is zero. Skip the whole series. continue; - double fLogicYForNextPoint = 0.0; - ///iterate through all points to create shapes - for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ ) - { - double fLogicInnerRadius, fLogicOuterRadius; - - ///compute the maximum relative distance offset of the current slice - ///from the pie center - ///it is worth noting that after the first invocation the maximum - ///offset value is cached, so it is evaluated only once per each - ///call to `createShapes` - double fOffset = getMaxOffset(); - - ///compute the outer and the inner radius for the current ring slice - bool bIsVisible = m_aPosHelper.getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset ); - if( !bIsVisible ) - continue; - - aParam.mfDepth = getTransformedDepth() * (n3DRelativeHeight / 100.0); - - rtl::Reference xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget); - ///collect data point information (logic coordinates, style ): - double fLogicYValue = fabs(pSeries->getYValue( nPointIndex )); - if( std::isnan(fLogicYValue) ) - continue; - if(fLogicYValue==0.0)//@todo: continue also if the resolution is too small - continue; - double fLogicYPos = fLogicYForNextPoint; - fLogicYForNextPoint += fLogicYValue; - - uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex ); - - //iterate through all subsystems to create partial points - { - //logic values on angle axis: - double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum; - double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum; - - ///note that the explode percentage is set to the `Offset` - ///property of the current data series entry only for slices - ///belonging to the outer ring - aParam.mfExplodePercentage = 0.0; - bool bDoExplode = ( nExplodeableSlot == static_cast< std::vector< VDataSeriesGroup >::size_type >(fSlotX) ); - if(bDoExplode) try - { - xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage; - } - catch( const uno::Exception& ) - { - TOOLS_WARN_EXCEPTION("chart2", "" ); - } - - ///see notes for `PolarPlottingPositionHelper` methods - ///transform to unit circle: - aParam.mfUnitCircleWidthAngleDegree = m_aPosHelper.getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue ); - aParam.mfUnitCircleStartAngleDegree = m_aPosHelper.transformToAngleDegree( fLogicStartAngleValue ); - aParam.mfUnitCircleInnerRadius = m_aPosHelper.transformToRadius( fLogicInnerRadius ); - aParam.mfUnitCircleOuterRadius = m_aPosHelper.transformToRadius( fLogicOuterRadius ); - - ///create data point - aParam.mfLogicZ = -1.0; // For 3D pie chart label position - - // Do concentric explosion if it's a donut chart with more than one series - const bool bConcentricExplosion = m_bUseRings && (m_aZSlots.front().size() > 1); - rtl::Reference xPointShape = - createDataPoint( - xSeriesGroupShape_Shapes, xPointProperties, aParam, nPointCount, - bConcentricExplosion); - - ///point color: - if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is()) - { - xPointShape->setPropertyValue("FillColor", - uno::Any(m_xColorScheme->getColorByIndex( nPointIndex ))); - } - - - if(bHasFillColorMapping) - { - double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); - if(!std::isnan(nPropVal)) - { - xPointShape->setPropertyValue("FillColor", uno::Any(static_cast( nPropVal))); - } - } - - ///create label - createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam); - - if(!bDoExplode) - { - ShapeFactory::setShapeName( xPointShape - , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) ); - } - else try - { - ///enable dragging of outer segments - - double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0; - double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius; - drawing::Position3D aOrigin = m_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ ); - drawing::Position3D aNewOrigin = m_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ ); - - sal_Int32 nOffsetPercent( static_cast(aParam.mfExplodePercentage * 100.0) ); - - awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition( - aOrigin, m_xLogicTarget, m_nDimension ) ); - awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition( - aNewOrigin, m_xLogicTarget, m_nDimension ) ); - - //enable dragging of piesegments - OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT - , pSeries->getSeriesParticle() - , ObjectIdentifier::getPieSegmentDragMethodServiceName() - , ObjectIdentifier::createPieSegmentDragParameterString( - nOffsetPercent, aMinimumPosition, aMaximumPosition ) - ) ); - - ShapeFactory::setShapeName( xPointShape - , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) ); - } - catch( const uno::Exception& ) - { - TOOLS_WARN_EXCEPTION("chart2", "" ); - } - }//next series in x slot (next y slot) - }//next category + switch (m_eSubType) { + case PieChartSubType_NONE: + createOneRing(SubPieType::NONE, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight); + break; + case PieChartSubType_BAR: + createOneRing(SubPieType::LEFT, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight); + break; + case PieChartSubType_PIE: + createOneRing(SubPieType::LEFT, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight); + break; + default: + assert(false); // this shouldn't happen + } }//next x slot } +void PieChart::createOneRing([[maybe_unused]]enum SubPieType eType, + double fSlotX, + ShapeParam& aParam, + const rtl::Reference& xSeriesTarget, + const rtl::Reference& xTextTarget, + VDataSeries* pSeries, + sal_Int32 n3DRelativeHeight) +{ + bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor"); + + /// The angle degree offset is set by the same property of the + /// data series. + /// Counter-clockwise offset from the 3 o'clock position. + m_aPosHelper.m_fAngleDegreeOffset = pSeries->getStartingAngle(); + + ///iterate through all points to get the sum of all entries of + ///the current data series + sal_Int32 nPointCount=pSeries->getTotalPointCount(); + + ///the `explodeable` ring is the first one except when the radius axis + ///orientation is reversed (always!?) and we are dealing with a donut: in + ///such a case the `explodeable` ring is the last one. + std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0; + if( m_aPosHelper.isMathematicalOrientationRadius() && m_bUseRings ) + nExplodeableSlot = m_aZSlots.front().size()-1; + + double fLogicYForNextPoint = 0.0; + ///iterate through all points to create shapes + for(sal_Int32 nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ ) + { + double fLogicInnerRadius, fLogicOuterRadius; + + ///compute the maximum relative distance offset of the current slice + ///from the pie center + ///it is worth noting that after the first invocation the maximum + ///offset value is cached, so it is evaluated only once per each + ///call to `createShapes` + double fOffset = getMaxOffset(); + + ///compute the outer and the inner radius for the current ring slice + bool bIsVisible = m_aPosHelper.getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset ); + if( !bIsVisible ) + continue; + + aParam.mfDepth = getTransformedDepth() * (n3DRelativeHeight / 100.0); + + rtl::Reference xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget); + ///collect data point information (logic coordinates, style ): + double fLogicYValue = fabs(pSeries->getYValue( nPointIndex )); + if( std::isnan(fLogicYValue) ) + continue; + if(fLogicYValue==0.0)//@todo: continue also if the resolution is too small + continue; + double fLogicYPos = fLogicYForNextPoint; + fLogicYForNextPoint += fLogicYValue; + + uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex ); + + //iterate through all subsystems to create partial points + { + //logic values on angle axis: + double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum; + double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum; + + ///note that the explode percentage is set to the `Offset` + ///property of the current data series entry only for slices + ///belonging to the outer ring + aParam.mfExplodePercentage = 0.0; + bool bDoExplode = ( nExplodeableSlot == static_cast< std::vector< VDataSeriesGroup >::size_type >(fSlotX) ); + if(bDoExplode) try + { + xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + + ///see notes for `PolarPlottingPositionHelper` methods + ///transform to unit circle: + aParam.mfUnitCircleWidthAngleDegree = m_aPosHelper.getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue ); + aParam.mfUnitCircleStartAngleDegree = m_aPosHelper.transformToAngleDegree( fLogicStartAngleValue ); + aParam.mfUnitCircleInnerRadius = m_aPosHelper.transformToRadius( fLogicInnerRadius ); + aParam.mfUnitCircleOuterRadius = m_aPosHelper.transformToRadius( fLogicOuterRadius ); + + ///create data point + aParam.mfLogicZ = -1.0; // For 3D pie chart label position + + // Do concentric explosion if it's a donut chart with more than one series + const bool bConcentricExplosion = m_bUseRings && (m_aZSlots.front().size() > 1); + rtl::Reference xPointShape = + createDataPoint( + xSeriesGroupShape_Shapes, xPointProperties, aParam, nPointCount, + bConcentricExplosion); + + ///point color: + if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is()) + { + xPointShape->setPropertyValue("FillColor", + uno::Any(m_xColorScheme->getColorByIndex( nPointIndex ))); + } + + + if(bHasFillColorMapping) + { + double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor"); + if(!std::isnan(nPropVal)) + { + xPointShape->setPropertyValue("FillColor", uno::Any(static_cast( nPropVal))); + } + } + + ///create label + createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam); + + if(!bDoExplode) + { + ShapeFactory::setShapeName( xPointShape + , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) ); + } + else try + { + ///enable dragging of outer segments + + double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0; + double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius; + drawing::Position3D aOrigin = m_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ ); + drawing::Position3D aNewOrigin = m_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ ); + + sal_Int32 nOffsetPercent( static_cast(aParam.mfExplodePercentage * 100.0) ); + + awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition( + aOrigin, m_xLogicTarget, m_nDimension ) ); + awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition( + aNewOrigin, m_xLogicTarget, m_nDimension ) ); + + //enable dragging of piesegments + OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT + , pSeries->getSeriesParticle() + , ObjectIdentifier::getPieSegmentDragMethodServiceName() + , ObjectIdentifier::createPieSegmentDragParameterString( + nOffsetPercent, aMinimumPosition, aMaximumPosition ) + ) ); + + ShapeFactory::setShapeName( xPointShape + , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + }//next series in x slot (next y slot) + }//next category +} + PieChart::PieLabelInfo::PieLabelInfo() : fValue(0.0) , bMovementAllowed(false), bMoved(false) diff --git a/chart2/source/view/charttypes/PieChart.hxx b/chart2/source/view/charttypes/PieChart.hxx index d6792f0b8479..5c04ee05cf86 100644 --- a/chart2/source/view/charttypes/PieChart.hxx +++ b/chart2/source/view/charttypes/PieChart.hxx @@ -75,6 +75,12 @@ public: virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override; virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override; + enum class SubPieType { + NONE, + LEFT, + RIGHT + }; + private: //methods rtl::Reference createDataPoint( @@ -122,6 +128,14 @@ struct PieLabelInfo; bool performLabelBestFitInnerPlacement( ShapeParam& rShapeParam , PieLabelInfo const & rPieLabelInfo ); + void createOneRing([[maybe_unused]]enum SubPieType eType + , double fSlotX + , ShapeParam& aParam + , const rtl::Reference& xSeriesTarget + , const rtl::Reference& xTextTarget + , VDataSeries* pSeries + , sal_Int32 n3DRelativeHeight); + private: //member PiePositionHelper m_aPosHelper; bool m_bUseRings;