/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
usingnamespace ::com::sun::star; using ::com::sun::star::uno::Reference; using ::basegfx::B2DVector; using ::basegfx::B2DPolygon; using ::basegfx::B2DPolyPolygon;
// For rotating the rectangle we use the opposite angle, // since `B2DHomMatrix` class used for // representing the transformation, performs rotations in the positive // direction (from the X axis to the Y axis). However since the coordinate // system used by the chart has the Y-axis pointing downward, a rotation in // the positive direction means a clockwise rotation. On the contrary text // labels are rotated counterclockwise. // The rotation is performed around the top-left vertex of the rectangle // which is then moved to its final position by using the top-left // vertex of the text label bounding box (aPos) as the translation vector.
::basegfx::B2DHomMatrix aMatrix;
aMatrix.rotate(-basegfx::deg2rad(fRotationAngleDegree));
aMatrix.translate( aPos.X, aPos.Y);
aPoly.transform( aMatrix );
}
/** * If the labels are staggered and bInnerLine is true we iterate through * only those labels that are closer to the diagram. * * If the labels are staggered and bInnerLine is false we iterate through * only those that are farther from the diagram. * * If the labels are not staggered we iterate through all labels.
*/ class LabelIterator : public TickIter
{ public:
LabelIterator( TickInfoArrayType& rTickInfoVector
, const AxisLabelStaggering eAxisLabelStaggering
, bool bInnerLine );
static B2DVector lcl_getLabelsDistance( TickIter& rIter, const B2DVector& rDistanceTickToText, double fRotationAngleDegree )
{ //calculates the height or width of a line of labels //thus a following line of labels can be shifted for that distance
static OUString getTextLabelString( const FixedNumberFormatter& rFixedNumberFormatter, const uno::Sequence<OUString>* pCategories, const TickInfo* pTickInfo, bool bComplexCat, Color& rExtraColor, bool& rHasExtraColor )
{ if (pCategories)
{ // This is a normal category axis. Get the label string from the // label string array.
sal_Int32 nIndex = static_cast<sal_Int32>(pTickInfo->getUnscaledTickValue()) - 1; //first category (index 0) matches with real number 1.0 if( nIndex>=0 && nIndex<pCategories->getLength() ) return (*pCategories)[nIndex];
return OUString();
} elseif (bComplexCat)
{ // This is a complex category axis. The label is stored in the tick. return pTickInfo->aText;
}
// This is a numeric axis. Format the original tick value per number format. return rFixedNumberFormatter.getFormattedString(pTickInfo->getUnscaledTickValue(), rExtraColor, rHasExtraColor);
}
/** * Iterate through only 3 ticks including the one that has the longest text * length. When the first tick has the longest text, it iterates through * the first 3 ticks. Otherwise it iterates through 3 ticks such that the * 2nd tick is the one with the longest text.
*/ class MaxLabelTickIter : public TickIter
{ public:
MaxLabelTickIter( TickInfoArrayType& rTickInfoVector, size_t nLongestLabelIndex );
bool canAutoAdjustLabelPlacement( const AxisLabelProperties& rAxisLabelProperties, bool bIsHorizontalAxis, bool bIsVerticalAxis)
{ // joined prerequisite checks for auto rotate and auto stagger if( rAxisLabelProperties.m_bOverlapAllowed ) returnfalse; if( rAxisLabelProperties.m_bLineBreakAllowed ) // auto line break may conflict with... returnfalse; if( rAxisLabelProperties.m_fRotationAngleDegree != 0.0 ) returnfalse; // automatic adjusting labels only works for // horizontal axis with horizontal text // or vertical axis with vertical text if( bIsHorizontalAxis ) return !rAxisLabelProperties.m_bStackCharacters; if( bIsVerticalAxis ) return rAxisLabelProperties.m_bStackCharacters; returnfalse;
}
// make clear that we check for auto rotation prerequisites constauto& isAutoRotatingOfLabelsAllowed = canAutoAdjustLabelPlacement;
} // namespace void VCartesianAxis::createAllTickInfosFromComplexCategories( TickInfoArraysType& rAllTickInfos, bool bShiftedPosition )
{ //no minor tickmarks will be generated! //order is: inner labels first , outer labels last (that is different to all other TickIter cases) if(!bShiftedPosition)
{
rAllTickInfos.clear();
sal_Int32 nLevel=0;
sal_Int32 nLevelCount = m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoryLevelCount(); for( ; nLevel<nLevelCount; nLevel++ )
{
TickInfoArrayType aTickInfoVector; const std::vector<ComplexCategory>* pComplexCategories =
m_aAxisProperties.m_pExplicitCategoriesProvider->getCategoriesByLevel(nLevel);
if (bIsBreakOfLabelsAllowed)
{ if (!m_aAxisProperties.m_bLimitSpaceForLabels)
{
basegfx::B2DVector nDeltaVector = pTickFactory->getXaxisEndPos() - pTickFactory->getXaxisStartPos();
nLimitedSpaceForText = nDeltaVector.getX();
} if (nScreenDistanceBetweenTicks > 0)
nLimitedSpaceForText = nScreenDistanceBetweenTicks;
if( bIsStaggered )
nLimitedSpaceForText *= 2;
if( nLimitedSpaceForText > 0 )
{ //reduce space for a small amount to have a visible distance between the labels:
sal_Int32 nReduce = (nLimitedSpaceForText*5)/100; if(!nReduce)
nReduce = 1;
nLimitedSpaceForText -= nReduce;
}
// recalculate the nLimitedSpaceForText in case of 90 and 270 degree if the text break is true if ( rAxisLabelProperties.m_fRotationAngleDegree == 90.0 || rAxisLabelProperties.m_fRotationAngleDegree == 270.0 )
{
nLimitedSpaceForText = rAxisLabelProperties.m_aMaximumSpaceForLabels.Height;
m_aAxisProperties.m_bLimitSpaceForLabels = false;
}
// recalculate the nLimitedSpaceForText in case of vertical category axis if the text break is true if ( m_aAxisProperties.m_bSwapXAndY && bIsVerticalAxis && rAxisLabelProperties.m_fRotationAngleDegree == 0.0 )
{
nLimitedSpaceForText = pTickFactory->getXaxisStartPos().getX();
m_aAxisProperties.m_bLimitSpaceForLabels = false;
}
}
// Stores an array of text label strings in case of a normal // (non-complex) category axis. const uno::Sequence<OUString>* pCategories = nullptr; if( m_bUseTextLabels && !m_aAxisProperties.m_bComplexCategories )
pCategories = &m_aTextLabels;
//don't create labels which does not fit into the rhythm if( nTick%rAxisLabelProperties.m_nRhythm != 0 ) continue;
//don't create labels for invisible ticks if( !pTickInfo->bPaintIt ) continue;
if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
{ // Overlapping is not allowed. If the label overlaps with its // neighboring label, try increasing the tick interval (or rhythm // as it's called) and start over.
if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
, rAxisLabelProperties.m_fRotationAngleDegree
, pTickInfo->aTickScreenPosition ) )
{ // This tick overlaps with its neighbor. Try to stagger (if // auto staggering is allowed) to avoid overlapping.
if (bOverlapsAfterAutoStagger)
{ // Still overlaps with its neighbor even after staggering. // Increment the visible tick intervals (if that's // allowed) and start over.
// Label has multiple lines and the words are broken if (nLimitedSpaceForText > 0
&& !rAxisLabelProperties.m_bOverlapAllowed
&& rAxisLabelProperties.m_fRotationAngleDegree == 0.0
&& nTick > 0
&& lcl_hasWordBreak(pTickInfo->xTextShape))
{ // Label has multiple lines and belongs to a complex category // axis. Rotate 90 degrees to try to avoid overlaps. if ( m_aAxisProperties.m_bComplexCategories )
{
rAxisLabelProperties.m_fRotationAngleDegree = 90;
}
rAxisLabelProperties.m_bLineBreakAllowed = false;
m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree;
removeTextShapesFromTicks(); returnfalse;
}
//if NO OVERLAP -> remove overlapping shapes if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
{ // Check if the label still overlaps with its neighbor. if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
{ // It overlaps. Check if staggering helps. bool bOverlapsAfterAutoStagger = true; if( !bIsStaggered && isAutoStaggeringOfLabelsAllowed( rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis ) )
{ // Compatibility option: starting from LibreOffice 5.1 the rotated // layout is preferred to staggering for axis labels. if( !isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis)
|| m_aAxisProperties.m_bTryStaggeringFirst )
{
bIsStaggered = true;
rAxisLabelProperties.m_eStaggering = AxisLabelStaggering::StaggerEven;
pLastVisibleNeighbourTickInfo = pPREPreviousVisibleTickInfo; if( !pLastVisibleNeighbourTickInfo ||
!lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
, rAxisLabelProperties.m_fRotationAngleDegree
, pTickInfo->aTickScreenPosition ) )
bOverlapsAfterAutoStagger = false;
}
}
if (bOverlapsAfterAutoStagger)
{ // Staggering didn't solve the overlap. if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
{ // Try auto-rotating the labels at 45 degrees and // start over. This rotation angle will be stored for // all future text shape creation runs. // The nRhythm parameter is reset to 1 since the layout // used for text labels is changed.
rAxisLabelProperties.autoRotate45();
m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
removeTextShapesFromTicks();
rAxisLabelProperties.m_nRhythm = 1; returnfalse;
}
// Try incrementing the tick interval and start over.
rAxisLabelProperties.m_nRhythm++;
removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget ); returnfalse;
}
}
}
// Stores an array of text label strings in case of a normal // (non-complex) category axis. const uno::Sequence<OUString>* pCategories = nullptr; if( m_bUseTextLabels && !m_aAxisProperties.m_bComplexCategories )
pCategories = &m_aTextLabels;
//don't create labels which does not fit into the rhythm if( nTick%rAxisLabelProperties.m_nRhythm != 0 ) continue;
//don't create labels for invisible ticks if( !pTickInfo->bPaintIt ) continue;
if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
{ // Overlapping is not allowed. If the label overlaps with its // neighboring label, try increasing the tick interval (or rhythm // as it's called) and start over.
if( lcl_doesShapeOverlapWithTickmark( *pLastVisibleNeighbourTickInfo->xTextShape
, rAxisLabelProperties.m_fRotationAngleDegree
, pTickInfo->aTickScreenPosition ) )
{ // This tick overlaps with its neighbor. Increment the visible // tick intervals (if that's allowed) and start over.
//if NO OVERLAP -> remove overlapping shapes if( pLastVisibleNeighbourTickInfo && !rAxisLabelProperties.m_bOverlapAllowed )
{ // Check if the label still overlaps with its neighbor. if( doesOverlap( pLastVisibleNeighbourTickInfo->xTextShape, pTickInfo->xTextShape, rAxisLabelProperties.m_fRotationAngleDegree ) )
{ // It overlaps. if( isAutoRotatingOfLabelsAllowed(rAxisLabelProperties, bIsHorizontalAxis, bIsVerticalAxis) )
{ // Try auto-rotating the labels at 45 degrees and // start over. This rotation angle will be stored for // all future text shape creation runs. // The nRhythm parameter is reset to 1 since the layout // used for text labels is changed.
rAxisLabelProperties.autoRotate45();
m_aAxisLabelProperties.m_fRotationAngleDegree = rAxisLabelProperties.m_fRotationAngleDegree; // Store it for future runs.
removeTextShapesFromTicks();
rAxisLabelProperties.m_nRhythm = 1; returnfalse;
}
// Try incrementing the tick interval and start over.
rAxisLabelProperties.m_nRhythm++;
removeShapesAtWrongRhythm( rTickIter, rAxisLabelProperties.m_nRhythm, nTick, xTarget ); returnfalse;
}
}
void VCartesianAxis::get2DAxisMainLine(
B2DVector& rStart, B2DVector& rEnd, AxisLabelAlignment& rAlignment, doublefCrossesOtherAxis ) const
{ //m_aAxisProperties might get updated and changed here because // the label alignment and inner direction sign depends exactly of the choice of the axis line position which is made here in this method
//only those points are candidates which are lying on exactly one wall as these are outer edges
tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fMinX, fYOnYPlane, fZOther ), getScreenPosAndLogicPos( fMinX, fYOther, fZOnZPlane ) };
//only those points are candidates which are lying on exactly one wall as these are outer edges
tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOnXPlane, fMinY, fZOther ), getScreenPosAndLogicPos( fXOther, fMinY, fZOnZPlane ) };
//only those points are candidates which are lying on exactly one wall as these are outer edges
tScreenPosAndLogicPosList aPosList { getScreenPosAndLogicPos( fXOther, fYOnYPlane, fMinZ ), getScreenPosAndLogicPos( fXOnXPlane, fYOther, fMinZ ) };
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.