diff options
author | Marco Cecchetti <marco.cecchetti@collabora.com> | 2015-04-23 09:56:49 +0200 |
---|---|---|
committer | Andras Timar <andras.timar@collabora.com> | 2015-05-04 09:06:14 +0000 |
commit | ea374ab51e229bb1a959a271c1405ef72ad71316 (patch) | |
tree | 8451ab92dcb380fd59ba430a7741d8f9296bab26 /chart2 | |
parent | a21a0b6dceaf965673ae601318e77991919c8f6a (diff) |
tdf#90839 - added support for inside placement for the best fit case
Change-Id: I4cd47d843e6892edfa43c37c131dde9cd324579a
Reviewed-on: https://gerrit.libreoffice.org/15520
Reviewed-by: Andras Timar <andras.timar@collabora.com>
Tested-by: Andras Timar <andras.timar@collabora.com>
Diffstat (limited to 'chart2')
-rw-r--r-- | chart2/source/view/charttypes/PieChart.cxx | 438 | ||||
-rw-r--r-- | chart2/source/view/charttypes/PieChart.hxx | 4 |
2 files changed, 442 insertions, 0 deletions
diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx index 21233648dcbc..4ba5ce7a377f 100644 --- a/chart2/source/view/charttypes/PieChart.cxx +++ b/chart2/source/view/charttypes/PieChart.cxx @@ -357,7 +357,15 @@ void PieChart::createTextLabelShape( aPieLabelInfo.bMovementAllowed = bMovementAllowed; aPieLabelInfo.bMoved= false; aPieLabelInfo.xTextTarget = xTextTarget; + + if (bMovementAllowed) + { + performLabelBestFit(rParam, aPieLabelInfo); + } + + m_aLabelInfoList.push_back(aPieLabelInfo); + } void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ ) @@ -723,6 +731,28 @@ bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt return true; } +inline +double lcl_radToDeg(double fAngleRad) +{ + return (fAngleRad / M_PI) * 180.0; +} + +inline +double lcl_degToRad(double fAngleDeg) +{ + return (fAngleDeg / 180) * M_PI; +} + +inline +double lcl_getDegAngleInStandardRange(double fAngle) +{ + while( fAngle < 0.0 ) + fAngle += 360.0; + while( fAngle >= 360.0 ) + fAngle -= 360.0; + return fAngle; +} + }//end anonymous namespace PieChart::PieLabelInfo::PieLabelInfo() @@ -1154,6 +1184,414 @@ void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSi } } + +/** Handle the placement of the label in the best fit case: + * the routine try to place the label inside the related pie slice, + * in case of success it returns true else returns false. + * + * Notation: + * C: the pie center + * s: the bisector ray of the current pie slice + * alpha: the angle between the horizontal axis and the bisector ray s + * N: the vertex of the label b.b. which is nearest to C + * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border + * P, Q: the intersection points between the label b.b. and the bisector ray s; + * P is the one at minimum distance respect with C + * e: the edge of the label b.b. where P lies (the nearest edge to C) + * M: the vertex of e that is not N + * G: the vertex of the label b.b. which is adjacent to N and that is not M + * beta: the angle MPF + * theta: the angle CPF + * + * + * | + * | /s + * | / + * | / + * | G _________________________/____________________________ F + * | | /Q ..| + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / d. . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . . | + * | | / . \ beta . | + * | |__________/._\___|_______.____________________________| + * | N /P / . M + * | /___/theta . + * | / . + * | / . r + * | / . + * | / . + * | / . + * | / . + * | / . + * | / . + * | / . + * | / . + * | /\. alpha + * __|/__|_____________________________________________________________ + * |C + * | + * + * + * When alpha = 45k (k integer) s crosses the label b.b. at N exactly. + * In such a case the nearest edge e is defined as the edge having N as the + * start vertex and that is covered in the counterclockwise direction when + * we move from N to the adjacent vertex. + * + * The nearest vertex N is: + * 1. the bottom left vertex when 0 < alpha < 90 + * 2. the bottom right vertex when 90 < alpha < 180 + * 3. the top right vertex when 180 < alpha < 270 + * 4. the top left vertex when 270 < alpha < 360. + * + * The nearest edge e is: + * 1. the left edge when −45 < alpha < 45 + * 2. the bottom edge when 45 < alpha <135 + * 3. the right edge when 135 < alpha < 225 + * 4. the top edge when 225 < alpha < 315. + * + **/ +bool PieChart::performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo) +{ + SAL_INFO( "chart2.pie.label.bestfit.inside", + "** PieChart::performLabelBestFitInnerPlacement invoked **" ); + + // get pie slice properties + double fStartAngleDeg = lcl_getDegAngleInStandardRange(rShapeParam.mfUnitCircleStartAngleDegree); + double fWidthAngleDeg = rShapeParam.mfUnitCircleWidthAngleDegree; + double fHalfWidthAngleDeg = fWidthAngleDeg / 2.0; + double fBisectingRayAngleDeg = lcl_getDegAngleInStandardRange(fStartAngleDeg + fHalfWidthAngleDeg); + + // get the middle point of the arc representing the pie slice border + double fLogicZ = rShapeParam.mfLogicZ + 1.0; + awt::Point aMiddleArcPoint = PlottingPositionHelper::transformSceneToScreenPosition( + m_pPosHelper->transformUnitCircleToScene( + fBisectingRayAngleDeg, + rShapeParam.mfUnitCircleOuterRadius, + fLogicZ ), + m_xLogicTarget, m_pShapeFactory, m_nDimension ); + + // compute the pie radius + basegfx::B2IVector aPieCenter = rPieLabelInfo.aOrigin; + basegfx::B2IVector aRadiusVector( + aMiddleArcPoint.X - aPieCenter.getX(), + aMiddleArcPoint.Y - aPieCenter.getY() ); + double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector); + double fPieRadius = sqrt( fSquaredPieRadius ); + + // the bb is moved as much as possible near to the border of the pie, + // anyway a small offset from the border is present (0.025 * pie radius) + const double fPieBorderOffset = 0.025; + fPieRadius = fPieRadius - fPieRadius * fPieBorderOffset; + + SAL_INFO( "chart2.pie.label.bestfit.inside", + " pie sector:" ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " start angle = " << fStartAngleDeg ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " angle width = " << fWidthAngleDeg ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " bisecting ray angle = " << fBisectingRayAngleDeg ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " pie radius = " << fPieRadius ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " pie center = " << rPieLabelInfo.aOrigin ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " middle arc point = (" << aMiddleArcPoint.X << "," + << aMiddleArcPoint.Y << ")" ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " label bounding box:" ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " old anchor point = " << rPieLabelInfo.aFirstPosition ); + + + if( ::rtl::math::approxEqual( fPieRadius, 0.0 ) ) + return false; + + // get label b.b. width and height + ::basegfx::B2IRectangle aBb( lcl_getRect( rPieLabelInfo.xLabelGroupShape ) ); + double fLabelWidth = aBb.getWidth(); + double fLabelHeight = aBb.getHeight(); + + // -45 <= fAlphaDeg < 315 + double fAlphaDeg = lcl_getDegAngleInStandardRange(fBisectingRayAngleDeg + 45) - 45; + double fAlphaRad = lcl_degToRad(fAlphaDeg); + + // compute nearest edge index + // 0 left + // 1 bottom + // 2 right + // 3 top + int nSectorIndex = floor( (fAlphaDeg + 45) / 45.0 ); + int nNearestEdgeIndex = nSectorIndex / 2; + + // compute lengths of the nearest edge and of the orthogonal edges + double fNearestEdgeLength = fLabelWidth; + double fOrthogonalEdgeLength = fLabelHeight; + int nAxisIndex = 0; + int nOrthogonalAxisIndex = 1; + if( nNearestEdgeIndex % 2 == 0 ) // nearest edge is vertical + { + fNearestEdgeLength = fLabelHeight; + fOrthogonalEdgeLength = fLabelWidth; + nAxisIndex = 1; + nOrthogonalAxisIndex = 0; + } + + // compute the distance between N and P + // such a distance is piece wise linear respect with alpha: + // given 45k <= alpha < 45(k+1) we have + // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45) + // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45) + int nIndex = nSectorIndex -1; // nIndex = -1...6 + double fIndexMod2 = (nIndex + 8) % 2; // fIndexMod2 must be non negative + double fSgn = 2.0 * (fIndexMod2 - 0.5); // 0 -> -1, 1 -> 1 + double fDistanceNP = (fNearestEdgeLength / 2.0) * (1 + fSgn * ((fAlphaDeg - 45 * (nIndex + fIndexMod2)) / 45.0)); + double fDistancePM = fNearestEdgeLength - fDistanceNP; + + // compute the length of the diagonal vector d, + // that is the distance between P and F + double fSquaredDistancePF = fDistancePM * fDistancePM + fOrthogonalEdgeLength * fOrthogonalEdgeLength; + double fDistancePF = sqrt( fSquaredDistancePF ); + + SAL_INFO( "chart2.pie.label.bestfit.inside", + " width = " << fLabelWidth ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " height = " << fLabelHeight ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " nearest edge index = " << nNearestEdgeIndex ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " alpha = " << fAlphaDeg ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " distance(N,P) = " << fDistanceNP ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " nIndex = " << nIndex ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " fIndexMod2 = " << fIndexMod2 ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " fSgn = " << fSgn ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " distance(P,F) = " << fDistancePF ); + + + // we check that the condition length(d) <= pie radius holds + if (fDistancePF > fPieRadius) + { + return false; + } + + // compute beta: the angle of the diagonal vector d, + // that is, the angle in P respect with the triangle PMF; + // since both arguments are non negative the returned value is in [0, PI/2] + double fBetaRad = atan2( fOrthogonalEdgeLength, fDistancePM ); + + // compute the theta angle, that is the angle in P + // respect with the triangle CFP; + // when the second intersection edge is opposite to the nearest edge, + // theta depends on alpha and beta according to the following relation: + // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta + // where i is the nearest edge index and s is the sign of (alpha' - 45), + // with alpha' = (alpha + 45) mod 90; + // when the second intersection edge is adjacent to the nearest edge, + // we have theta = 360 - f(alpha, beta); + // note that in the former case 0 <= f(alpha, beta) <= 180, + // whilst in the latter case 180 <= f(alpha, beta) <= 360; + double fAlphaMod90 = fmod( fAlphaDeg + 45, 90.0 ) - 45; + double fSign = ::rtl::math::approxEqual( fAlphaMod90, 0.0 ) + ? 0.0 + : ( fAlphaMod90 < 0 ) ? -1.0 : 1.0; + double fThetaRad = fSign * fAlphaRad + M_PI_2 * (1 - fSign * nNearestEdgeIndex) + fBetaRad; + if( fThetaRad > M_PI ) + { + fThetaRad = 2 * M_PI - fThetaRad; + } + + // compute the length of the positional vector, + // that is the distance between C and P + double fDistanceCP; + // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0 + if( ::rtl::math::approxEqual( fmod(fThetaRad, M_PI), 0.0 )) + { + fDistanceCP = fPieRadius - fDistancePF; + } + else // general case + { + // we can compute d(C,P) by applying some trigonometric formula to + // the triangle CFP : we know length(d) and length(r) = r and we have + // computed the angle in P (theta); so named delta the angle in C and + // gamma the angle in F, by the relation: + // + // r d(P,F) d(C,P) + // --------- = --------- = --------- + // sin theta sin delta sin gamma + // + // we get the wanted distance + double fSinTheta = sin( fThetaRad ); + double fSinDelta = fDistancePF * fSinTheta / fPieRadius; + double fDeltaRad = asin( fSinDelta ); + double fGammaRad = M_PI - (fThetaRad + fDeltaRad); + double fSinGamma = sin( fGammaRad ); + fDistanceCP = fPieRadius * fSinGamma / fSinTheta; + } + + // define the positional vector + basegfx::B2DVector aPositionalVector( cos(fAlphaRad), sin(fAlphaRad) ); + aPositionalVector.setLength(fDistanceCP); + + // we define a direction vector in order to know + // in which quadrant we are working + basegfx::B2DVector aDirection(1.0, 1.0); + if( 90 <= fBisectingRayAngleDeg && fBisectingRayAngleDeg < 270 ) + { + aDirection.setX(-1.0); + } + if( fBisectingRayAngleDeg >= 180 ) + { + aDirection.setY(-1.0); + } + + // compute vertices N, M and G respect with pie center C + basegfx::B2DVector aNearestVertex(aPositionalVector); + aNearestVertex[nAxisIndex] += -aDirection[nAxisIndex] * fDistanceNP; + basegfx::B2DVector aVertexM(aNearestVertex); + aVertexM[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength; + basegfx::B2DVector aVertexG(aNearestVertex); + aVertexG[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength; + + SAL_INFO( "chart2.pie.label.bestfit.inside", + " beta = " << lcl_radToDeg(fBetaRad) ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " theta = " << lcl_radToDeg(fThetaRad) ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " fAlphaMod90 = " << fAlphaMod90 ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " fSign = " << fSign ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " distance(C,P) = " << fDistanceCP ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " direction vector = " << aDirection ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " N = " << aNearestVertex ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " M = " << aVertexM ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " G = " << aVertexG ); + + // in order to be able to place the label inside the pie slice we need + // to check that each angle between s and the ray starting from C and + // passing through a b.b. vertex is less than half width of the pie slice; + // when the nearest edge e crosses a Cartesian axis it is sufficient + // to test only the vertices belonging to e, else we need to test + // the 2 vertices that aren’t either N or F . Note that if a b.b. edge + // crosses a Cartesian axis then it is the nearest edge to C + + // check the angle between CP and CM + double fAngleRad = aPositionalVector.angle(aVertexM); + double fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) ); + if( fAngleDeg > 180 ) // in case the wrong angle has been computed + fAngleDeg = 360 - fAngleDeg; + SAL_INFO( "chart2.pie.label.bestfit.inside", + " angle between CP and CM: " << fAngleDeg ); + if( fAngleDeg > fHalfWidthAngleDeg ) + { + return false; + } + + if( ( aNearestVertex[nAxisIndex] >= 0 && aVertexM[nAxisIndex] <= 0 ) + || ( aNearestVertex[nAxisIndex] <= 0 && aVertexM[nAxisIndex] >= 0 ) ) + { + // check the angle between CP and CN + fAngleRad = aPositionalVector.angle(aNearestVertex); + fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) ); + if( fAngleDeg > 180 ) // in case the wrong angle has been computed + fAngleDeg = 360 - fAngleDeg; + SAL_INFO( "chart2.pie.label.bestfit.inside", + " angle between CP and CN: " << fAngleDeg ); + if( fAngleDeg > fHalfWidthAngleDeg ) + { + return false; + } + } + else + { + // check the angle between CP and CG + fAngleRad = aPositionalVector.angle(aVertexG); + fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) ); + if( fAngleDeg > 180 ) // in case the wrong angle has been computed + fAngleDeg = 360 - fAngleDeg; + SAL_INFO( "chart2.pie.label.bestfit.inside", + " angle between CP and CG: " << fAngleDeg ); + if( fAngleDeg > fHalfWidthAngleDeg ) + { + return false; + } + } + + // compute the b.b. center respect with the pie center + basegfx::B2DVector aBBCenter(aNearestVertex); + aBBCenter[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength / 2; + aBBCenter[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength / 2; + + // compute the b.b. anchor point + basegfx::B2IVector aNewAnchorPoint = aPieCenter; + aNewAnchorPoint[0] += floor(aBBCenter[0]); + aNewAnchorPoint[1] -= floor(aBBCenter[1]); // the Y axis on the screen points downward + + // compute the translation vector for moving the label from the current + // screen position to the new one + basegfx::B2IVector aTranslationVector = aNewAnchorPoint - rPieLabelInfo.aFirstPosition; + + // compute the new screen position and move the label + awt::Point aNewPos( rPieLabelInfo.xLabelGroupShape->getPosition() ); + aNewPos.X += aTranslationVector.getX(); + aNewPos.Y += aTranslationVector.getY(); + rPieLabelInfo.xLabelGroupShape->setPosition(aNewPos); + + SAL_INFO( "chart2.pie.label.bestfit.inside", + " center = " << aBBCenter ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " new anchor point = " << aNewAnchorPoint ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " translation vector = " << aTranslationVector ); + SAL_INFO( "chart2.pie.label.bestfit.inside", + " new position = (" << aNewPos.X << "," << aNewPos.Y << ")" ); + + return true; +} + +/** Handle the outer placement of the labels in the best fit case. + * + */ +bool PieChart::performLabelBestFitOuterPlacement(ShapeParam& /*rShapeParam*/, PieLabelInfo& /*rPieLabelInfo*/) +{ + SAL_WARN( "chart2.pie.label.bestfit", "to be implemented" ); + return false; +} + +/** Handle the placement of the label in the best fit case. + * First off the routine try to place the label inside the related pie slice, + * if this is not possible the label is placed outside. + */ +void PieChart::performLabelBestFit(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo) +{ + if( m_bUseRings ) + return; + + if( !performLabelBestFitInnerPlacement(rShapeParam, rPieLabelInfo) ) + { + performLabelBestFitOuterPlacement(rShapeParam, rPieLabelInfo); + } +} + } //namespace chart /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/charttypes/PieChart.hxx b/chart2/source/view/charttypes/PieChart.hxx index 20bdd7eff782..901e2f1fcc07 100644 --- a/chart2/source/view/charttypes/PieChart.hxx +++ b/chart2/source/view/charttypes/PieChart.hxx @@ -108,6 +108,10 @@ struct PieLabelInfo; , PieLabelInfo* pCenter, bool bSingleCenter, bool& rbAlternativeMoveDirection , const ::com::sun::star::awt::Size& rPageSize ); + bool performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo); + bool performLabelBestFitOuterPlacement(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo); + void performLabelBestFit(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo); + private: //member PiePositionHelper* m_pPosHelper; bool m_bUseRings; |