diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /chart2/source/view/main/VLegend.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'chart2/source/view/main/VLegend.cxx')
-rw-r--r-- | chart2/source/view/main/VLegend.cxx | 1122 |
1 files changed, 1122 insertions, 0 deletions
diff --git a/chart2/source/view/main/VLegend.cxx b/chart2/source/view/main/VLegend.cxx new file mode 100644 index 000000000..d16495e42 --- /dev/null +++ b/chart2/source/view/main/VLegend.cxx @@ -0,0 +1,1122 @@ +/* -*- 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 . + */ + +#include "VLegend.hxx" +#include "VButton.hxx" +#include <PropertyMapper.hxx> +#include <ChartModel.hxx> +#include <ObjectIdentifier.hxx> +#include <RelativePositionHelper.hxx> +#include <ShapeFactory.hxx> +#include <RelativeSizeHelper.hxx> +#include <LegendEntryProvider.hxx> +#include <chartview/DrawModelWrapper.hxx> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/chart/ChartLegendExpansion.hpp> +#include <com/sun/star/chart2/LegendPosition.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/XFormattedString2.hpp> +#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp> +#include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp> +#include <rtl/math.hxx> +#include <svl/languageoptions.hxx> +#include <tools/diagnose_ex.h> + +#include <vector> +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; + +namespace chart +{ + +namespace +{ + +typedef std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues; + +double lcl_CalcViewFontSize( + const Reference< beans::XPropertySet > & xProp, + const awt::Size & rReferenceSize ) +{ + double fResult = 10.0; + + awt::Size aPropRefSize; + float fFontHeight( 0.0 ); + if( xProp.is() && ( xProp->getPropertyValue( "CharHeight") >>= fFontHeight )) + { + fResult = fFontHeight; + try + { + if( (xProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) && + (aPropRefSize.Height > 0)) + { + fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + + // pt -> 1/100th mm + return (fResult * (2540.0 / 72.0)); +} + +void lcl_getProperties( + const Reference< beans::XPropertySet > & xLegendProp, + tPropertyValues & rOutLineFillProperties, + tPropertyValues & rOutTextProperties, + const awt::Size & rReferenceSize ) +{ + // Get Line- and FillProperties from model legend + if( !xLegendProp.is()) + return; + + // set rOutLineFillProperties + ::chart::tPropertyNameValueMap aLineFillValueMap; + ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp ); + + aLineFillValueMap[ "LineJoint" ] <<= drawing::LineJoint_ROUND; + + ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( + rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap ); + + // set rOutTextProperties + ::chart::tPropertyNameValueMap aTextValueMap; + ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp ); + + aTextValueMap[ "TextAutoGrowHeight" ] <<= true; + aTextValueMap[ "TextAutoGrowWidth" ] <<= true; + aTextValueMap[ "TextHorizontalAdjust" ] <<= drawing::TextHorizontalAdjust_LEFT; + aTextValueMap[ "TextMaximumFrameWidth" ] <<= rReferenceSize.Width; //needs to be overwritten by actual available space in the legend + + // recalculate font size + awt::Size aPropRefSize; + float fFontHeight( 0.0 ); + if( (xLegendProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) && + (aPropRefSize.Height > 0) && + (aTextValueMap[ "CharHeight" ] >>= fFontHeight) ) + { + aTextValueMap[ "CharHeight" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + + if( aTextValueMap[ "CharHeightAsian" ] >>= fFontHeight ) + { + aTextValueMap[ "CharHeightAsian" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + } + if( aTextValueMap[ "CharHeightComplex" ] >>= fFontHeight ) + { + aTextValueMap[ "CharHeightComplex" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + } + } + + ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( + rOutTextProperties.first, rOutTextProperties.second, aTextValueMap ); +} + +awt::Size lcl_createTextShapes( + const std::vector<ViewLegendEntry> & rEntries, + const Reference< lang::XMultiServiceFactory > & xShapeFactory, + const Reference< drawing::XShapes > & xTarget, + std::vector< Reference< drawing::XShape > > & rOutTextShapes, + const tPropertyValues & rTextProperties ) +{ + awt::Size aResult; + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + + for (ViewLegendEntry const & rEntry : rEntries) + { + try + { + OUString aLabelString; + Sequence< Reference< XFormattedString2 > > aLabelSeq = rEntry.aLabel; + for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i ) + { + // todo: support more than one text range + if( i == 1 ) + break; + + aLabelString += aLabelSeq[i]->getString(); + // workaround for Issue #i67540# + if( aLabelString.isEmpty()) + aLabelString = " "; + } + + Reference< drawing::XShape > xEntry = + pShapeFactory->createText( xTarget, aLabelString, + rTextProperties.first, rTextProperties.second, uno::Any() ); + + // adapt max-extent + awt::Size aCurrSize( xEntry->getSize()); + aResult.Width = std::max( aResult.Width, aCurrSize.Width ); + aResult.Height = std::max( aResult.Height, aCurrSize.Height ); + + rOutTextShapes.push_back( xEntry ); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + + return aResult; +} + +void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns, + const std::vector< Reference< drawing::XShape > >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth ) +{ + rColumnWidths.clear(); + sal_Int32 nNumberOfEntries = rTextShapes.size(); + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow ) + { + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn ) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < nNumberOfEntries ) + { + awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() ); + sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width; + if( nRow==0 ) + rColumnWidths.push_back( nWidth ); + else + rColumnWidths[nColumn] = std::max( nWidth, rColumnWidths[nColumn] ); + } + } + } +} + +void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns, + const std::vector< Reference< drawing::XShape > >& rTextShapes ) +{ + // calculate maximum height for each row + // and collect column widths + rRowHeights.clear(); + sal_Int32 nNumberOfEntries = rTextShapes.size(); + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nCurrentRowHeight = 0; + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < nNumberOfEntries ) + { + awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() ); + nCurrentRowHeight = std::max( nCurrentRowHeight, aTextSize.Height ); + } + } + rRowHeights.push_back( nCurrentRowHeight ); + } +} + +sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize ) +{ + const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize ); + if (!nFontHeight) + return 0; + sal_Int32 nTextLineHeight = nFontHeight; + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nFullTextHeight = aRowHeights[nRow]; + if( ( nFullTextHeight / nFontHeight ) <= 1 ) + { + nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height + break; + } + } + return nTextLineHeight; +} + +//returns resulting legend size +awt::Size lcl_placeLegendEntries( + std::vector<ViewLegendEntry> & rEntries, + css::chart::ChartLegendExpansion eExpansion, + bool bSymbolsLeftSide, + double fViewFontSize, + const awt::Size& rMaxSymbolExtent, + tPropertyValues & rTextProperties, + const Reference< drawing::XShapes > & xTarget, + const Reference< lang::XMultiServiceFactory > & xShapeFactory, + const awt::Size& rRemainingSpace, + sal_Int32 nYStartPosition, + const awt::Size& rPageSize, + bool bIsPivotChart, + awt::Size& rDefaultLegendSize) +{ + bool bIsCustomSize = (eExpansion == css::chart::ChartLegendExpansion_CUSTOM); + awt::Size aResultingLegendSize(0,0); + // For Pivot charts set the *minimum* legend size as a function of page size. + if ( bIsPivotChart ) + aResultingLegendSize = awt::Size((rPageSize.Width * 13) / 80, (rPageSize.Height * 31) / 90); + if( bIsCustomSize ) + aResultingLegendSize = awt::Size(rRemainingSpace.Width, rRemainingSpace.Height + nYStartPosition); + + // #i109336# Improve auto positioning in chart + sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) ); + sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) ); + sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); + sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); + + const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm + const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance; + sal_Int32 nMaxTextWidth = rRemainingSpace.Width - nSymbolPlusDistanceWidth; + uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, "TextMaximumFrameWidth"); + if(pFrameWidthAny) + { + if( eExpansion == css::chart::ChartLegendExpansion_HIGH ) + { + // limit the width of texts to 30% of the total available width + // #i109336# Improve auto positioning in chart + nMaxTextWidth = rRemainingSpace.Width * 3 / 10; + } + *pFrameWidthAny <<= nMaxTextWidth; + } + + std::vector< Reference< drawing::XShape > > aTextShapes; + awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties ); + OSL_ASSERT( aTextShapes.size() == rEntries.size()); + + sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width; + sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height; + sal_Int32 nNumberOfEntries = rEntries.size(); + + rDefaultLegendSize.Width = nMaxEntryWidth; + rDefaultLegendSize.Height = nMaxEntryHeight + nYPadding; + + sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0; + std::vector< sal_Int32 > aColumnWidths; + std::vector< sal_Int32 > aRowHeights; + + sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize ); + + // determine layout depending on LegendExpansion + if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM ) + { + sal_Int32 nCurrentRow=0; + sal_Int32 nCurrentColumn=-1; + sal_Int32 nMaxColumnCount=-1; + for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ ) + { + Reference< drawing::XShape > xShape( aTextShapes[nN] ); + if( !xShape.is() ) + continue; + awt::Size aSize( xShape->getSize() ); + sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth; + sal_Int32 nCurrentColumnCount = aColumnWidths.size(); + + //are we allowed to add a new column? + if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount ) + { + //try add a new column + nCurrentColumn++; + if( nCurrentColumn < nCurrentColumnCount ) + { + //check whether the current column width is sufficient for the new entry + if( aColumnWidths[nCurrentColumn]>=nNewWidth ) + { + //all good proceed with next entry + continue; + } + } + if( nCurrentColumn < nCurrentColumnCount ) + aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] ); + else + aColumnWidths.push_back(nNewWidth); + + //do the columns still fit into the given size? + nCurrentColumnCount = aColumnWidths.size();//update count + sal_Int32 nSumWidth = 0; + for (sal_Int32 nColumn = 0; nColumn < nCurrentColumnCount; nColumn++) + nSumWidth += aColumnWidths[nColumn]; + + if( nSumWidth <= rRemainingSpace.Width || nCurrentColumnCount==1 ) + { + //all good proceed with next entry + continue; + } + else + { + //not enough space for the current amount of columns + //try again with less columns + nMaxColumnCount = nCurrentColumnCount-1; + nN=-1; + nCurrentRow=0; + nCurrentColumn=-1; + aColumnWidths.clear(); + } + } + else + { + //add a new row and try the same entry again + nCurrentRow++; + nCurrentColumn=-1; + nN--; + } + } + nNumberOfColumns = aColumnWidths.size(); + nNumberOfRows = nCurrentRow+1; + + //check if there is not enough space so that some entries must be removed + lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); + nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); + sal_Int32 nSumHeight = 0; + for (sal_Int32 nRow=0; nRow < nNumberOfRows; nRow++) + nSumHeight += aRowHeights[nRow]; + sal_Int32 nRemainingSpace = rRemainingSpace.Height - nSumHeight; + + if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404 + { + //remove entries that are too big + for (sal_Int32 nRow = nNumberOfRows; nRow--; ) + { + for (sal_Int32 nColumn = nNumberOfColumns; nColumn--; ) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) ) + { + DrawModelWrapper::removeShape( aTextShapes[nEntry] ); + aTextShapes.pop_back(); + } + if( nEntry < nNumberOfEntries && ( nEntry != 0 || nNumberOfColumns != 1 ) ) + { + DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol ); + rEntries.pop_back(); + nNumberOfEntries--; + } + } + if (nRow == 0 && nNumberOfColumns == 1) + { + try + { + OUString aLabelString = rEntries[0].aLabel[0]->getString(); + const OUString sDots = "..."; + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + for (sal_Int32 nNewLen = aLabelString.getLength() - sDots.getLength(); nNewLen > 0; nNewLen--) + { + OUString aNewLabel = aLabelString.copy(0, nNewLen) + sDots; + Reference<drawing::XShape> xEntry = pShapeFactory->createText( + xTarget, aNewLabel, rTextProperties.first, rTextProperties.second, uno::Any()); + nSumHeight = xEntry->getSize().Height; + nRemainingSpace = rRemainingSpace.Height - nSumHeight; + if (nRemainingSpace >= 0) + { + sal_Int32 nWidth = xEntry->getSize().Width + nSymbolPlusDistanceWidth; + if (rRemainingSpace.Width - nWidth >= 0) + { + aTextShapes.push_back(xEntry); + rEntries[0].aLabel[0]->setString(aNewLabel); + aRowHeights[0] = nSumHeight; + aColumnWidths[0] = nWidth; + break; + } + } + DrawModelWrapper::removeShape(xEntry); + } + if (aTextShapes.size() == 0) + { + DrawModelWrapper::removeShape(rEntries[0].aSymbol); + rEntries.pop_back(); + nNumberOfEntries--; + aRowHeights.pop_back(); + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + else + { + nSumHeight -= aRowHeights[nRow]; + aRowHeights.pop_back(); + nRemainingSpace = rRemainingSpace.Height - nSumHeight; + if (nRemainingSpace >= 0) + break; + } + } + nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size()); + } + if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404 + { + sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset; + if( nRemainingSpace < nNormalSpacingHeight ) + { + //reduce spacing between the entries + nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1); + } + else + { + //we have some space left that should be spread equally between all rows + sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1); + nYPadding += nRemainingSingleSpace; + nYOffset += nRemainingSingleSpace; + } + } + + //check spacing between columns + sal_Int32 nSumWidth = 0; + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; nColumn++) + nSumWidth += aColumnWidths[nColumn]; + nRemainingSpace = rRemainingSpace.Width - nSumWidth; + if( nRemainingSpace>=0 ) + { + sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset; + if( nRemainingSpace < nNormalSpacingWidth ) + { + //reduce spacing between the entries + nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1); + } + else + { + //we have some space left that should be spread equally between all columns + sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1); + nXPadding += nRemainingSingleSpace; + nXOffset += nRemainingSingleSpace; + } + } + } + else if( eExpansion == css::chart::ChartLegendExpansion_HIGH ) + { + sal_Int32 nMaxNumberOfRows = nMaxEntryHeight + ? (rRemainingSpace.Height - 2*nYPadding ) / nMaxEntryHeight + : 0; + + nNumberOfColumns = nMaxNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nMaxNumberOfRows ) )) + : 0; + nNumberOfRows = nNumberOfColumns + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfColumns ) )) + : 0; + } + else if( eExpansion == css::chart::ChartLegendExpansion_WIDE ) + { + sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth + ? (rRemainingSpace.Width - 2*nXPadding ) / nMaxEntryWidth + : 0; + + nNumberOfRows = nMaxNumberOfColumns + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nMaxNumberOfColumns ) )) + : 0; + nNumberOfColumns = nNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfRows ) )) + : 0; + } + else // css::chart::ChartLegendExpansion_BALANCED + { + double fAspect = nMaxEntryHeight + ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight ) + : 0.0; + + nNumberOfRows = static_cast< sal_Int32 >( + ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect ))); + nNumberOfColumns = nNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfRows ) )) + : 0; + } + + if(nNumberOfRows<=0) + return aResultingLegendSize; + + if( eExpansion != css::chart::ChartLegendExpansion_CUSTOM ) + { + lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth ); + lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); + nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); + } + + sal_Int32 nCurrentXPos = bSymbolsLeftSide ? nXPadding : -nXPadding; + + // place entries into column and rows + sal_Int32 nMaxYPos = 0; + + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn) + { + sal_Int32 nCurrentYPos = nYPadding + nYStartPosition; + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry >= nNumberOfEntries ) + break; + + // text shape + Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] ); + if( xTextShape.is() ) + { + awt::Size aTextSize( xTextShape->getSize() ); + sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth; + if( !bSymbolsLeftSide ) + nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width; + xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos )); + } + + // symbol + Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); + if( xSymbol.is() ) + { + awt::Size aSymbolSize( rMaxSymbolExtent ); + sal_Int32 nSymbolXPos = nCurrentXPos; + if( !bSymbolsLeftSide ) + nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width; + sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 ); + xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) ); + } + + nCurrentYPos += aRowHeights[ nRow ]; + if( nRow+1 < nNumberOfRows ) + nCurrentYPos += nYOffset; + nMaxYPos = std::max( nMaxYPos, nCurrentYPos ); + } + if( bSymbolsLeftSide ) + { + nCurrentXPos += aColumnWidths[nColumn]; + if( nColumn+1 < nNumberOfColumns ) + nCurrentXPos += nXOffset; + } + else + { + nCurrentXPos -= aColumnWidths[nColumn]; + if( nColumn+1 < nNumberOfColumns ) + nCurrentXPos -= nXOffset; + } + } + + if( !bIsCustomSize ) + { + if( bSymbolsLeftSide ) + aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nCurrentXPos + nXPadding ); + else + { + sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding); + aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nLegendWidth ); + } + aResultingLegendSize.Height = std::max( aResultingLegendSize.Height, nMaxYPos + nYPadding ); + } + + if( !bSymbolsLeftSide ) + { + sal_Int32 nLegendWidth = aResultingLegendSize.Width; + awt::Point aPos(0,0); + for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ ) + { + Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); + aPos = xSymbol->getPosition(); + aPos.X += nLegendWidth; + xSymbol->setPosition( aPos ); + Reference< drawing::XShape > xText( aTextShapes[ nEntry ] ); + aPos = xText->getPosition(); + aPos.X += nLegendWidth; + xText->setPosition( aPos ); + } + } + + return aResultingLegendSize; +} + +// #i109336# Improve auto positioning in chart +sal_Int32 lcl_getLegendLeftRightMargin() +{ + return 210; // 1/100 mm +} + +// #i109336# Improve auto positioning in chart +sal_Int32 lcl_getLegendTopBottomMargin() +{ + return 185; // 1/100 mm +} + +chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize ) +{ + chart2::RelativePosition aResult; + + switch( ePos ) + { + case LegendPosition_LINE_START: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) / + static_cast< double >( rPageSize.Width ); + aResult = chart2::RelativePosition( + fDefaultDistance, 0.5, drawing::Alignment_LEFT ); + } + break; + case LegendPosition_LINE_END: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) / + static_cast< double >( rPageSize.Width ); + aResult = chart2::RelativePosition( + 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT ); + } + break; + case LegendPosition_PAGE_START: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) / + static_cast< double >( rPageSize.Height ); + double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance; + aResult = chart2::RelativePosition( + 0.5, fDistance, drawing::Alignment_TOP ); + } + break; + case LegendPosition_PAGE_END: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) / + static_cast< double >( rPageSize.Height ); + + double fDistance = double(rPageSize.Height - (rOutAvailableSpace.Y + rOutAvailableSpace.Height)); + fDistance += fDefaultDistance; + fDistance /= double(rPageSize.Height); + + aResult = chart2::RelativePosition( + 0.5, 1.0 - fDistance, drawing::Alignment_BOTTOM ); + } + break; + case LegendPosition::LegendPosition_MAKE_FIXED_SIZE: + default: + // nothing to be set + break; + } + + return aResult; +} + +/** @return + a point relative to the upper left corner that can be used for + XShape::setPosition() +*/ +awt::Point lcl_calculatePositionAndRemainingSpace( + awt::Rectangle & rRemainingSpace, + const awt::Size & rPageSize, + const chart2::RelativePosition& rRelPos, + LegendPosition ePos, + const awt::Size& aLegendSize, + bool bOverlay ) +{ + // calculate position + awt::Point aResult( + static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ), + static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height )); + + aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( + aResult, aLegendSize, rRelPos.Anchor ); + + // adapt rRemainingSpace if LegendPosition is not CUSTOM + // #i109336# Improve auto positioning in chart + sal_Int32 nXDistance = lcl_getLegendLeftRightMargin(); + sal_Int32 nYDistance = lcl_getLegendTopBottomMargin(); + if (!bOverlay) switch( ePos ) + { + case LegendPosition_LINE_START: + { + sal_Int32 nExtent = aLegendSize.Width; + rRemainingSpace.Width -= ( nExtent + nXDistance ); + rRemainingSpace.X += ( nExtent + nXDistance ); + } + break; + case LegendPosition_LINE_END: + { + rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance ); + } + break; + case LegendPosition_PAGE_START: + { + sal_Int32 nExtent = aLegendSize.Height; + rRemainingSpace.Height -= ( nExtent + nYDistance ); + rRemainingSpace.Y += ( nExtent + nYDistance ); + } + break; + case LegendPosition_PAGE_END: + { + rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance ); + } + break; + + default: + // nothing + break; + } + + // adjust the legend position. Esp. for old files that had slightly smaller legends + const sal_Int32 nEdgeDistance( 30 ); + if( aResult.X + aLegendSize.Width > rPageSize.Width ) + { + sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance ); + if( nNewX > rPageSize.Width / 4 ) + aResult.X = nNewX; + } + if( aResult.Y + aLegendSize.Height > rPageSize.Height ) + { + sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance ); + if( nNewY > rPageSize.Height / 4 ) + aResult.Y = nNewY; + } + + return aResult; +} + +bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode ) +{ + bool bSymbolsLeftSide = true; + try + { + if( SvtLanguageOptions().IsCTLFontEnabled() ) + { + if(xLegendProp.is()) + { + sal_Int16 nWritingMode=-1; + if( xLegendProp->getPropertyValue( "WritingMode" ) >>= nWritingMode ) + { + if( nWritingMode == text::WritingMode2::PAGE ) + nWritingMode = nDefaultWritingMode; + if( nWritingMode == text::WritingMode2::RL_TB ) + bSymbolsLeftSide=false; + } + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + return bSymbolsLeftSide; +} + +std::vector<std::shared_ptr<VButton>> lcl_createButtons( + uno::Reference<drawing::XShapes> const & xLegendContainer, + uno::Reference<lang::XMultiServiceFactory> const & xShapeFactory, + ChartModel& rModel, bool bPlaceButtonsVertically, long & nUsedHeight) +{ + std::vector<std::shared_ptr<VButton>> aButtons; + + uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY); + if (!xPivotTableDataProvider.is()) + return aButtons; + + if (!xPivotTableDataProvider->getColumnFields().hasElements()) + return aButtons; + + awt::Size aSize(2000, 700); + int x = 100; + int y = 100; + + const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getColumnFields(); + for (chart2::data::PivotTableFieldEntry const & sColumnFieldEntry : aPivotFieldEntries) + { + auto pButton = std::make_shared<VButton>(); + aButtons.push_back(pButton); + pButton->init(xLegendContainer, xShapeFactory); + awt::Point aNewPosition(x, y); + pButton->setLabel(sColumnFieldEntry.Name); + pButton->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry.DimensionIndex)); + pButton->setPosition(aNewPosition); + pButton->setSize(aSize); + if (sColumnFieldEntry.Name == "Data") + { + pButton->showArrow(false); + pButton->setBGColor(Color(0x00F6F6F6)); + } + if (sColumnFieldEntry.HasHiddenMembers) + pButton->setArrowColor(Color(0x0000FF)); + + if (bPlaceButtonsVertically) + y += aSize.Height + 100; + else + x += aSize.Width + 100; + } + if (bPlaceButtonsVertically) + nUsedHeight += y + 100; + else + nUsedHeight += aSize.Height + 100; + + return aButtons; +} + +} // anonymous namespace + +VLegend::VLegend( + const Reference< XLegend > & xLegend, + const Reference< uno::XComponentContext > & xContext, + const std::vector< LegendEntryProvider* >& rLegendEntryProviderList, + const Reference< drawing::XShapes >& xTargetPage, + const Reference< lang::XMultiServiceFactory >& xFactory, + ChartModel& rModel ) + : m_xTarget(xTargetPage) + , m_xShapeFactory(xFactory) + , m_xLegend(xLegend) + , mrModel(rModel) + , m_xContext(xContext) + , m_aLegendEntryProviderList(rLegendEntryProviderList) + , m_nDefaultWritingMode(text::WritingMode2::LR_TB) +{ +} + +void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode ) +{ + m_nDefaultWritingMode = nDefaultWritingMode; +} + +bool VLegend::isVisible( const Reference< XLegend > & xLegend ) +{ + if( ! xLegend.is()) + return false; + + bool bShow = false; + try + { + Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW ); + xLegendProp->getPropertyValue( "Show") >>= bShow; + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + + return bShow; +} + +void VLegend::createShapes( + const awt::Size & rAvailableSpace, + const awt::Size & rPageSize, + awt::Size & rDefaultLegendSize ) +{ + if(! (m_xLegend.is() && + m_xShapeFactory.is() && + m_xTarget.is())) + return; + + try + { + //create shape and add to page + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( mrModel ) ); + m_xShape.set( pShapeFactory->createGroup2D( m_xTarget, + ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle )), + uno::UNO_QUERY); + + // create and insert sub-shapes + Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY ); + if( xLegendContainer.is()) + { + // for quickly setting properties + tPropertyValues aLineFillProperties; + tPropertyValues aTextProperties; + + Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY ); + css::chart::ChartLegendExpansion eExpansion = css::chart::ChartLegendExpansion_HIGH; + awt::Size aLegendSize( rAvailableSpace ); + + bool bCustom = false; + LegendPosition eLegendPosition = LegendPosition_LINE_END; + if (xLegendProp.is()) + { + // get Expansion property + xLegendProp->getPropertyValue("Expansion") >>= eExpansion; + if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM ) + { + RelativeSize aRelativeSize; + if (xLegendProp->getPropertyValue("RelativeSize") >>= aRelativeSize) + { + aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width )); + aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height )); + bCustom = true; + } + else + { + eExpansion = css::chart::ChartLegendExpansion_HIGH; + } + } + xLegendProp->getPropertyValue("AnchorPosition") >>= eLegendPosition; + lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize ); + } + + // create entries + double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo + // #i109336# Improve auto positioning in chart + sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 ); + sal_Int32 nSymbolWidth = nSymbolHeight; + + for (LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList) + { + if (pLegendEntryProvider) + { + awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio(); + sal_Int32 nCurrentWidth = aCurrentRatio.Width; + if( aCurrentRatio.Height > 0 ) + { + nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height; + } + nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth ); + } + } + awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight ); + + std::vector<ViewLegendEntry> aViewEntries; + for(LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList) + { + if (pLegendEntryProvider) + { + std::vector<ViewLegendEntry> aNewEntries = pLegendEntryProvider->createLegendEntries( + aMaxSymbolExtent, eLegendPosition, xLegendProp, + xLegendContainer, m_xShapeFactory, m_xContext, mrModel); + if (aNewEntries.size() == 0) + return; + aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() ); + } + } + + bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode ); + + uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider( mrModel.getDataProvider(), uno::UNO_QUERY ); + bool bIsPivotChart = xPivotTableDataProvider.is(); + + if ( !aViewEntries.empty() || bIsPivotChart ) + { + // create buttons + long nUsedButtonHeight = 0; + bool bPlaceButtonsVertically = (eLegendPosition != LegendPosition_PAGE_START && + eLegendPosition != LegendPosition_PAGE_END && + eExpansion != css::chart::ChartLegendExpansion_WIDE); + + std::vector<std::shared_ptr<VButton>> aButtons = lcl_createButtons(xLegendContainer, m_xShapeFactory, mrModel, bPlaceButtonsVertically, nUsedButtonHeight); + + // A custom size includes the size we used for buttons already, so we need to + // subtract that from the size that is available for the legend + if (bCustom) + aLegendSize.Height -= nUsedButtonHeight; + + // place the legend entries + aLegendSize = lcl_placeLegendEntries(aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, + aMaxSymbolExtent, aTextProperties, xLegendContainer, + m_xShapeFactory, aLegendSize, nUsedButtonHeight, rPageSize, bIsPivotChart, rDefaultLegendSize); + + uno::Reference<beans::XPropertySet> xModelPage(mrModel.getPageBackground()); + + for (std::shared_ptr<VButton> const & pButton : aButtons) + { + // adjust the width of the buttons if we place them vertically + if (bPlaceButtonsVertically) + pButton->setSize({aLegendSize.Width - 200, pButton->getSize().Height}); + + // create the buttons + pButton->createShapes(xModelPage); + } + } + + Reference< drawing::XShape > xBorder = + pShapeFactory->createRectangle( xLegendContainer, + aLegendSize, + awt::Point(0,0), + aLineFillProperties.first, + aLineFillProperties.second, ShapeFactory::StackPosition::Bottom ); + + //because of this name this border will be used for marking the legend + ShapeFactory::setShapeName( xBorder, "MarkHandles" ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +void VLegend::changePosition( + awt::Rectangle & rOutAvailableSpace, + const awt::Size & rPageSize, + const css::awt::Size & rDefaultLegendSize ) +{ + if(! m_xShape.is()) + return; + + try + { + // determine position and alignment depending on default position + awt::Size aLegendSize = m_xShape->getSize(); + Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW ); + chart2::RelativePosition aRelativePosition; + + bool bDefaultLegendSize = rDefaultLegendSize.Width != 0 || rDefaultLegendSize.Height != 0; + bool bAutoPosition = + ! (xLegendProp->getPropertyValue( "RelativePosition") >>= aRelativePosition); + + LegendPosition ePos = LegendPosition_LINE_END; + xLegendProp->getPropertyValue( "AnchorPosition") >>= ePos; + + bool bOverlay = false; + xLegendProp->getPropertyValue("Overlay") >>= bOverlay; + //calculate position + if( bAutoPosition ) + { + // auto position: relative to remaining space + aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); + awt::Point aPos = lcl_calculatePositionAndRemainingSpace( + rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize, bOverlay ); + m_xShape->setPosition( aPos ); + } + else + { + // manual position: relative to whole page + awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height ); + awt::Point aPos = lcl_calculatePositionAndRemainingSpace( + aAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay ); + m_xShape->setPosition( aPos ); + + if (!bOverlay) + { + // calculate remaining space as if having autoposition: + aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); + lcl_calculatePositionAndRemainingSpace( + rOutAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay ); + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |