Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / chart2 / source / view / main / VLegend.cxx
blobc8edeeed00a04c293b5efa9b3a1ef728970546ea
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "VLegend.hxx"
21 #include "VButton.hxx"
22 #include <PropertyMapper.hxx>
23 #include <ChartModel.hxx>
24 #include <ObjectIdentifier.hxx>
25 #include <RelativePositionHelper.hxx>
26 #include <ShapeFactory.hxx>
27 #include <RelativeSizeHelper.hxx>
28 #include <LegendEntryProvider.hxx>
29 #include <chartview/DrawModelWrapper.hxx>
30 #include <com/sun/star/text/WritingMode2.hpp>
31 #include <com/sun/star/beans/XPropertySet.hpp>
32 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
33 #include <com/sun/star/drawing/LineJoint.hpp>
34 #include <com/sun/star/drawing/XShapes.hpp>
35 #include <com/sun/star/chart/ChartLegendExpansion.hpp>
36 #include <com/sun/star/chart2/LegendPosition.hpp>
37 #include <com/sun/star/chart2/RelativePosition.hpp>
38 #include <com/sun/star/chart2/RelativeSize.hpp>
39 #include <com/sun/star/chart2/XFormattedString2.hpp>
40 #include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
41 #include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp>
42 #include <svl/languageoptions.hxx>
43 #include <tools/diagnose_ex.h>
45 #include <vector>
46 #include <algorithm>
48 using namespace ::com::sun::star;
49 using namespace ::com::sun::star::chart2;
51 using ::com::sun::star::uno::Reference;
52 using ::com::sun::star::uno::Sequence;
54 namespace chart
57 namespace
60 typedef std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues;
62 double lcl_CalcViewFontSize(
63 const Reference< beans::XPropertySet > & xProp,
64 const awt::Size & rReferenceSize )
66 double fResult = 10.0;
68 awt::Size aPropRefSize;
69 float fFontHeight( 0.0 );
70 if( xProp.is() && ( xProp->getPropertyValue( "CharHeight") >>= fFontHeight ))
72 fResult = fFontHeight;
73 try
75 if( (xProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
76 (aPropRefSize.Height > 0))
78 fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize );
81 catch( const uno::Exception & )
83 DBG_UNHANDLED_EXCEPTION("chart2");
87 // pt -> 1/100th mm
88 return (fResult * (2540.0 / 72.0));
91 void lcl_getProperties(
92 const Reference< beans::XPropertySet > & xLegendProp,
93 tPropertyValues & rOutLineFillProperties,
94 tPropertyValues & rOutTextProperties,
95 const awt::Size & rReferenceSize )
97 // Get Line- and FillProperties from model legend
98 if( xLegendProp.is())
100 // set rOutLineFillProperties
101 ::chart::tPropertyNameValueMap aLineFillValueMap;
102 ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
104 aLineFillValueMap[ "LineJoint" ] <<= drawing::LineJoint_ROUND;
106 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
107 rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
109 // set rOutTextProperties
110 ::chart::tPropertyNameValueMap aTextValueMap;
111 ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
113 aTextValueMap[ "TextAutoGrowHeight" ] <<= true;
114 aTextValueMap[ "TextAutoGrowWidth" ] <<= true;
115 aTextValueMap[ "TextHorizontalAdjust" ] <<= drawing::TextHorizontalAdjust_LEFT;
116 aTextValueMap[ "TextMaximumFrameWidth" ] <<= rReferenceSize.Width; //needs to be overwritten by actual available space in the legend
118 // recalculate font size
119 awt::Size aPropRefSize;
120 float fFontHeight( 0.0 );
121 if( (xLegendProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
122 (aPropRefSize.Height > 0) &&
123 (aTextValueMap[ "CharHeight" ] >>= fFontHeight) )
125 aTextValueMap[ "CharHeight" ] <<=
126 static_cast< float >(
127 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
129 if( aTextValueMap[ "CharHeightAsian" ] >>= fFontHeight )
131 aTextValueMap[ "CharHeightAsian" ] <<=
132 static_cast< float >(
133 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
135 if( aTextValueMap[ "CharHeightComplex" ] >>= fFontHeight )
137 aTextValueMap[ "CharHeightComplex" ] <<=
138 static_cast< float >(
139 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
143 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
144 rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
148 awt::Size lcl_createTextShapes(
149 const std::vector<ViewLegendEntry> & rEntries,
150 const Reference< lang::XMultiServiceFactory > & xShapeFactory,
151 const Reference< drawing::XShapes > & xTarget,
152 std::vector< Reference< drawing::XShape > > & rOutTextShapes,
153 const tPropertyValues & rTextProperties )
155 awt::Size aResult;
156 ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory);
158 for (ViewLegendEntry const & rEntry : rEntries)
162 OUString aLabelString;
163 Sequence< Reference< XFormattedString2 > > aLabelSeq = rEntry.aLabel;
164 for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i )
166 // todo: support more than one text range
167 if( i == 1 )
168 break;
170 aLabelString += aLabelSeq[i]->getString();
171 // workaround for Issue #i67540#
172 if( aLabelString.isEmpty())
173 aLabelString = " ";
176 Reference< drawing::XShape > xEntry =
177 pShapeFactory->createText( xTarget, aLabelString,
178 rTextProperties.first, rTextProperties.second, uno::Any() );
180 // adapt max-extent
181 awt::Size aCurrSize( xEntry->getSize());
182 aResult.Width = std::max( aResult.Width, aCurrSize.Width );
183 aResult.Height = std::max( aResult.Height, aCurrSize.Height );
185 rOutTextShapes.push_back( xEntry );
187 catch( const uno::Exception & )
189 DBG_UNHANDLED_EXCEPTION("chart2");
193 return aResult;
196 void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
197 const std::vector< Reference< drawing::XShape > >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth )
199 rColumnWidths.clear();
200 sal_Int32 nNumberOfEntries = rTextShapes.size();
201 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow )
203 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
205 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
206 if( nEntry < nNumberOfEntries )
208 awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
209 sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
210 if( nRow==0 )
211 rColumnWidths.push_back( nWidth );
212 else
213 rColumnWidths[nColumn] = std::max( nWidth, rColumnWidths[nColumn] );
219 void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
220 const std::vector< Reference< drawing::XShape > >& rTextShapes )
222 // calculate maximum height for each row
223 // and collect column widths
224 rRowHeights.clear();
225 sal_Int32 nNumberOfEntries = rTextShapes.size();
226 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
228 sal_Int32 nCurrentRowHeight = 0;
229 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
231 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
232 if( nEntry < nNumberOfEntries )
234 awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
235 nCurrentRowHeight = std::max( nCurrentRowHeight, aTextSize.Height );
238 rRowHeights.push_back( nCurrentRowHeight );
242 sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
244 const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
245 if (!nFontHeight)
246 return 0;
247 sal_Int32 nTextLineHeight = nFontHeight;
248 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
250 sal_Int32 nFullTextHeight = aRowHeights[nRow];
251 if( ( nFullTextHeight / nFontHeight ) <= 1 )
253 nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
254 break;
257 return nTextLineHeight;
260 //returns resulting legend size
261 awt::Size lcl_placeLegendEntries(
262 std::vector<ViewLegendEntry> & rEntries,
263 css::chart::ChartLegendExpansion eExpansion,
264 bool bSymbolsLeftSide,
265 double fViewFontSize,
266 const awt::Size& rMaxSymbolExtent,
267 tPropertyValues & rTextProperties,
268 const Reference< drawing::XShapes > & xTarget,
269 const Reference< lang::XMultiServiceFactory > & xShapeFactory,
270 const awt::Size& rRemainingSpace,
271 sal_Int32 nYStartPosition,
272 const awt::Size& rPageSize,
273 bool bIsPivotChart)
275 bool bIsCustomSize = (eExpansion == css::chart::ChartLegendExpansion_CUSTOM);
276 awt::Size aResultingLegendSize(0,0);
277 // For Pivot charts set the *minimum* legend size as a function of page size.
278 if ( bIsPivotChart )
279 aResultingLegendSize = awt::Size((rPageSize.Width * 13) / 80, (rPageSize.Height * 31) / 90);
280 if( bIsCustomSize )
281 aResultingLegendSize = awt::Size(rRemainingSpace.Width, rRemainingSpace.Height + nYStartPosition);
283 // #i109336# Improve auto positioning in chart
284 sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) );
285 sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
286 sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
287 sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
289 const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
290 const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
291 sal_Int32 nMaxTextWidth = rRemainingSpace.Width - (2 * nXPadding) - nSymbolPlusDistanceWidth;
292 uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, "TextMaximumFrameWidth");
293 if(pFrameWidthAny)
295 if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
297 // limit the width of texts to 30% of the total available width
298 // #i109336# Improve auto positioning in chart
299 nMaxTextWidth = rRemainingSpace.Width * 3 / 10;
301 *pFrameWidthAny <<= nMaxTextWidth;
304 std::vector< Reference< drawing::XShape > > aTextShapes;
305 awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties );
306 OSL_ASSERT( aTextShapes.size() == rEntries.size());
308 sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
309 sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
310 sal_Int32 nNumberOfEntries = rEntries.size();
312 sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
313 std::vector< sal_Int32 > aColumnWidths;
314 std::vector< sal_Int32 > aRowHeights;
316 sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
318 // determine layout depending on LegendExpansion
319 if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
321 sal_Int32 nCurrentRow=0;
322 sal_Int32 nCurrentColumn=-1;
323 sal_Int32 nMaxColumnCount=-1;
324 for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ )
326 Reference< drawing::XShape > xShape( aTextShapes[nN] );
327 if( !xShape.is() )
328 continue;
329 awt::Size aSize( xShape->getSize() );
330 sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth;
331 sal_Int32 nCurrentColumnCount = aColumnWidths.size();
333 //are we allowed to add a new column?
334 if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount )
336 //try add a new column
337 nCurrentColumn++;
338 if( nCurrentColumn < nCurrentColumnCount )
340 //check whether the current column width is sufficient for the new entry
341 if( aColumnWidths[nCurrentColumn]>=nNewWidth )
343 //all good proceed with next entry
344 continue;
347 if( nCurrentColumn < nCurrentColumnCount )
348 aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] );
349 else
350 aColumnWidths.push_back(nNewWidth);
352 //do the columns still fit into the given size?
353 nCurrentColumnCount = aColumnWidths.size();//update count
354 sal_Int32 nSumWidth = 0;
355 for (sal_Int32 nColumn = 0; nColumn < nCurrentColumnCount; nColumn++)
356 nSumWidth += aColumnWidths[nColumn];
358 if( nSumWidth <= rRemainingSpace.Width || nCurrentColumnCount==1 )
360 //all good proceed with next entry
361 continue;
363 else
365 //not enough space for the current amount of columns
366 //try again with less columns
367 nMaxColumnCount = nCurrentColumnCount-1;
368 nN=-1;
369 nCurrentRow=0;
370 nCurrentColumn=-1;
371 aColumnWidths.clear();
374 else
376 //add a new row and try the same entry again
377 nCurrentRow++;
378 nCurrentColumn=-1;
379 nN--;
382 nNumberOfColumns = aColumnWidths.size();
383 nNumberOfRows = nCurrentRow+1;
385 //check if there is not enough space so that some entries must be removed
386 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
387 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
388 sal_Int32 nSumHeight = 0;
389 for (sal_Int32 nRow=0; nRow < nNumberOfRows; nRow++)
390 nSumHeight += aRowHeights[nRow];
391 sal_Int32 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
393 if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404
395 //remove entries that are too big
396 for (sal_Int32 nRow = nNumberOfRows; nRow--; )
398 for (sal_Int32 nColumn = nNumberOfColumns; nColumn--; )
400 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
401 if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) )
403 DrawModelWrapper::removeShape( aTextShapes[nEntry] );
404 aTextShapes.pop_back();
406 if( nEntry < nNumberOfEntries )
408 DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol );
409 rEntries.pop_back();
410 nNumberOfEntries--;
413 nSumHeight -= aRowHeights[nRow];
414 aRowHeights.pop_back();
415 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
416 if( nRemainingSpace>=0 )
417 break;
419 nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size());
421 if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404
423 sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset;
424 if( nRemainingSpace < nNormalSpacingHeight )
426 //reduce spacing between the entries
427 nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1);
429 else
431 //we have some space left that should be spread equally between all rows
432 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1);
433 nYPadding += nRemainingSingleSpace;
434 nYOffset += nRemainingSingleSpace;
438 //check spacing between columns
439 sal_Int32 nSumWidth = 0;
440 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; nColumn++)
441 nSumWidth += aColumnWidths[nColumn];
442 nRemainingSpace = rRemainingSpace.Width - nSumWidth;
443 if( nRemainingSpace>=0 )
445 sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset;
446 if( nRemainingSpace < nNormalSpacingWidth )
448 //reduce spacing between the entries
449 nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1);
451 else
453 //we have some space left that should be spread equally between all columns
454 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1);
455 nXPadding += nRemainingSingleSpace;
456 nXOffset += nRemainingSingleSpace;
460 else if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
462 sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
463 ? (rRemainingSpace.Height - 2*nYPadding ) / nMaxEntryHeight
464 : 0;
466 nNumberOfColumns = nMaxNumberOfRows
467 ? static_cast< sal_Int32 >(
468 ceil( static_cast< double >( nNumberOfEntries ) /
469 static_cast< double >( nMaxNumberOfRows ) ))
470 : 0;
471 nNumberOfRows = nNumberOfColumns
472 ? static_cast< sal_Int32 >(
473 ceil( static_cast< double >( nNumberOfEntries ) /
474 static_cast< double >( nNumberOfColumns ) ))
475 : 0;
477 else if( eExpansion == css::chart::ChartLegendExpansion_WIDE )
479 sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth
480 ? (rRemainingSpace.Width - 2*nXPadding ) / nMaxEntryWidth
481 : 0;
483 nNumberOfRows = nMaxNumberOfColumns
484 ? static_cast< sal_Int32 >(
485 ceil( static_cast< double >( nNumberOfEntries ) /
486 static_cast< double >( nMaxNumberOfColumns ) ))
487 : 0;
488 nNumberOfColumns = nNumberOfRows
489 ? static_cast< sal_Int32 >(
490 ceil( static_cast< double >( nNumberOfEntries ) /
491 static_cast< double >( nNumberOfRows ) ))
492 : 0;
494 else // css::chart::ChartLegendExpansion_BALANCED
496 double fAspect = nMaxEntryHeight
497 ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight )
498 : 0.0;
500 nNumberOfRows = static_cast< sal_Int32 >(
501 ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect )));
502 nNumberOfColumns = nNumberOfRows
503 ? static_cast< sal_Int32 >(
504 ceil( static_cast< double >( nNumberOfEntries ) /
505 static_cast< double >( nNumberOfRows ) ))
506 : 0;
509 if(nNumberOfRows<=0)
510 return aResultingLegendSize;
512 if( eExpansion != css::chart::ChartLegendExpansion_CUSTOM )
514 lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
515 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
516 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
519 sal_Int32 nCurrentXPos = bSymbolsLeftSide ? nXPadding : -nXPadding;
521 // place entries into column and rows
522 sal_Int32 nMaxYPos = 0;
524 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
526 sal_Int32 nCurrentYPos = nYPadding + nYStartPosition;
527 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
529 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
530 if( nEntry >= nNumberOfEntries )
531 break;
533 // text shape
534 Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] );
535 if( xTextShape.is() )
537 awt::Size aTextSize( xTextShape->getSize() );
538 sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
539 if( !bSymbolsLeftSide )
540 nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
541 xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
544 // symbol
545 Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
546 if( xSymbol.is() )
548 awt::Size aSymbolSize( rMaxSymbolExtent );
549 sal_Int32 nSymbolXPos = nCurrentXPos;
550 if( !bSymbolsLeftSide )
551 nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
552 sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
553 xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
556 nCurrentYPos += aRowHeights[ nRow ];
557 if( nRow+1 < nNumberOfRows )
558 nCurrentYPos += nYOffset;
559 nMaxYPos = std::max( nMaxYPos, nCurrentYPos );
561 if( bSymbolsLeftSide )
563 nCurrentXPos += aColumnWidths[nColumn];
564 if( nColumn+1 < nNumberOfColumns )
565 nCurrentXPos += nXOffset;
567 else
569 nCurrentXPos -= aColumnWidths[nColumn];
570 if( nColumn+1 < nNumberOfColumns )
571 nCurrentXPos -= nXOffset;
575 if( !bIsCustomSize )
577 if( bSymbolsLeftSide )
578 aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nCurrentXPos + nXPadding );
579 else
581 sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
582 aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nLegendWidth );
584 aResultingLegendSize.Height = std::max( aResultingLegendSize.Height, nMaxYPos + nYPadding );
587 if( !bSymbolsLeftSide )
589 sal_Int32 nLegendWidth = aResultingLegendSize.Width;
590 awt::Point aPos(0,0);
591 for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ )
593 Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
594 aPos = xSymbol->getPosition();
595 aPos.X += nLegendWidth;
596 xSymbol->setPosition( aPos );
597 Reference< drawing::XShape > xText( aTextShapes[ nEntry ] );
598 aPos = xText->getPosition();
599 aPos.X += nLegendWidth;
600 xText->setPosition( aPos );
604 return aResultingLegendSize;
607 // #i109336# Improve auto positioning in chart
608 sal_Int32 lcl_getLegendLeftRightMargin()
610 return 210; // 1/100 mm
613 // #i109336# Improve auto positioning in chart
614 sal_Int32 lcl_getLegendTopBottomMargin()
616 return 185; // 1/100 mm
619 chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
621 chart2::RelativePosition aResult;
623 switch( ePos )
625 case LegendPosition_LINE_START:
627 // #i109336# Improve auto positioning in chart
628 const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
629 static_cast< double >( rPageSize.Width );
630 aResult = chart2::RelativePosition(
631 fDefaultDistance, 0.5, drawing::Alignment_LEFT );
633 break;
634 case LegendPosition_LINE_END:
636 // #i109336# Improve auto positioning in chart
637 const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
638 static_cast< double >( rPageSize.Width );
639 aResult = chart2::RelativePosition(
640 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
642 break;
643 case LegendPosition_PAGE_START:
645 // #i109336# Improve auto positioning in chart
646 const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
647 static_cast< double >( rPageSize.Height );
648 double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance;
649 aResult = chart2::RelativePosition(
650 0.5, fDistance, drawing::Alignment_TOP );
652 break;
653 case LegendPosition_PAGE_END:
655 // #i109336# Improve auto positioning in chart
656 const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
657 static_cast< double >( rPageSize.Height );
659 double fDistance = double(rPageSize.Height - (rOutAvailableSpace.Y + rOutAvailableSpace.Height));
660 fDistance += fDefaultDistance;
661 fDistance /= double(rPageSize.Height);
663 aResult = chart2::RelativePosition(
664 0.5, 1.0 - fDistance, drawing::Alignment_BOTTOM );
666 break;
668 case LegendPosition_CUSTOM:
669 // to avoid warning
670 case LegendPosition::LegendPosition_MAKE_FIXED_SIZE:
671 // nothing to be set
672 break;
675 return aResult;
678 /** @return
679 a point relative to the upper left corner that can be used for
680 XShape::setPosition()
682 awt::Point lcl_calculatePositionAndRemainingSpace(
683 awt::Rectangle & rRemainingSpace,
684 const awt::Size & rPageSize,
685 const chart2::RelativePosition& rRelPos,
686 LegendPosition ePos,
687 const awt::Size& aLegendSize )
689 // calculate position
690 awt::Point aResult(
691 static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ),
692 static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height ));
694 aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
695 aResult, aLegendSize, rRelPos.Anchor );
697 // adapt rRemainingSpace if LegendPosition is not CUSTOM
698 // #i109336# Improve auto positioning in chart
699 sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
700 sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
701 switch( ePos )
703 case LegendPosition_LINE_START:
705 sal_Int32 nExtent = aLegendSize.Width;
706 rRemainingSpace.Width -= ( nExtent + nXDistance );
707 rRemainingSpace.X += ( nExtent + nXDistance );
709 break;
710 case LegendPosition_LINE_END:
712 rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
714 break;
715 case LegendPosition_PAGE_START:
717 sal_Int32 nExtent = aLegendSize.Height;
718 rRemainingSpace.Height -= ( nExtent + nYDistance );
719 rRemainingSpace.Y += ( nExtent + nYDistance );
721 break;
722 case LegendPosition_PAGE_END:
724 rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance );
726 break;
728 default:
729 // nothing
730 break;
733 // adjust the legend position. Esp. for old files that had slightly smaller legends
734 const sal_Int32 nEdgeDistance( 30 );
735 if( aResult.X + aLegendSize.Width > rPageSize.Width )
737 sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance );
738 if( nNewX > rPageSize.Width / 4 )
739 aResult.X = nNewX;
741 if( aResult.Y + aLegendSize.Height > rPageSize.Height )
743 sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance );
744 if( nNewY > rPageSize.Height / 4 )
745 aResult.Y = nNewY;
748 return aResult;
751 bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
753 bool bSymbolsLeftSide = true;
756 if( SvtLanguageOptions().IsCTLFontEnabled() )
758 if(xLegendProp.is())
760 sal_Int16 nWritingMode=-1;
761 if( xLegendProp->getPropertyValue( "WritingMode" ) >>= nWritingMode )
763 if( nWritingMode == text::WritingMode2::PAGE )
764 nWritingMode = nDefaultWritingMode;
765 if( nWritingMode == text::WritingMode2::RL_TB )
766 bSymbolsLeftSide=false;
771 catch( const uno::Exception & )
773 DBG_UNHANDLED_EXCEPTION("chart2");
775 return bSymbolsLeftSide;
778 std::vector<std::shared_ptr<VButton>> lcl_createButtons(
779 uno::Reference<drawing::XShapes> const & xLegendContainer,
780 uno::Reference<lang::XMultiServiceFactory> const & xShapeFactory,
781 ChartModel& rModel, bool bPlaceButtonsVertically, long & nUsedHeight)
783 std::vector<std::shared_ptr<VButton>> aButtons;
785 uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY);
786 if (!xPivotTableDataProvider.is())
787 return aButtons;
789 if (!xPivotTableDataProvider->getColumnFields().hasElements())
790 return aButtons;
792 awt::Size aSize(2000, 700);
793 int x = 100;
794 int y = 100;
796 const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getColumnFields();
797 for (chart2::data::PivotTableFieldEntry const & sColumnFieldEntry : aPivotFieldEntries)
799 std::shared_ptr<VButton> pButton(new VButton);
800 aButtons.push_back(pButton);
801 pButton->init(xLegendContainer, xShapeFactory);
802 awt::Point aNewPosition(x, y);
803 pButton->setLabel(sColumnFieldEntry.Name);
804 pButton->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry.DimensionIndex));
805 pButton->setPosition(aNewPosition);
806 pButton->setSize(aSize);
807 if (sColumnFieldEntry.Name == "Data")
809 pButton->showArrow(false);
810 pButton->setBGColor(Color(0x00F6F6F6));
812 if (sColumnFieldEntry.HasHiddenMembers)
813 pButton->setArrowColor(Color(0x0000FF));
815 if (bPlaceButtonsVertically)
816 y += aSize.Height + 100;
817 else
818 x += aSize.Width + 100;
820 if (bPlaceButtonsVertically)
821 nUsedHeight += y + 100;
822 else
823 nUsedHeight += aSize.Height + 100;
825 return aButtons;
828 } // anonymous namespace
830 VLegend::VLegend(
831 const Reference< XLegend > & xLegend,
832 const Reference< uno::XComponentContext > & xContext,
833 const std::vector< LegendEntryProvider* >& rLegendEntryProviderList,
834 const Reference< drawing::XShapes >& xTargetPage,
835 const Reference< lang::XMultiServiceFactory >& xFactory,
836 ChartModel& rModel )
837 : m_xTarget(xTargetPage)
838 , m_xShapeFactory(xFactory)
839 , m_xLegend(xLegend)
840 , mrModel(rModel)
841 , m_xContext(xContext)
842 , m_aLegendEntryProviderList(rLegendEntryProviderList)
843 , m_nDefaultWritingMode(text::WritingMode2::LR_TB)
847 void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
849 m_nDefaultWritingMode = nDefaultWritingMode;
852 bool VLegend::isVisible( const Reference< XLegend > & xLegend )
854 if( ! xLegend.is())
855 return false;
857 bool bShow = false;
860 Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW );
861 xLegendProp->getPropertyValue( "Show") >>= bShow;
863 catch( const uno::Exception & )
865 DBG_UNHANDLED_EXCEPTION("chart2");
868 return bShow;
871 void VLegend::createShapes(
872 const awt::Size & rAvailableSpace,
873 const awt::Size & rPageSize )
875 if(! (m_xLegend.is() &&
876 m_xShapeFactory.is() &&
877 m_xTarget.is()))
878 return;
882 //create shape and add to page
883 ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory);
884 OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( mrModel ) );
885 m_xShape.set( pShapeFactory->createGroup2D( m_xTarget,
886 ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle )),
887 uno::UNO_QUERY);
889 // create and insert sub-shapes
890 Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY );
891 if( xLegendContainer.is())
893 // for quickly setting properties
894 tPropertyValues aLineFillProperties;
895 tPropertyValues aTextProperties;
897 Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY );
898 css::chart::ChartLegendExpansion eExpansion = css::chart::ChartLegendExpansion_HIGH;
899 awt::Size aLegendSize( rAvailableSpace );
901 bool bCustom = false;
902 LegendPosition eLegendPosition = LegendPosition_CUSTOM;
903 if (xLegendProp.is())
905 // get Expansion property
906 xLegendProp->getPropertyValue("Expansion") >>= eExpansion;
907 if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
909 RelativeSize aRelativeSize;
910 if (xLegendProp->getPropertyValue("RelativeSize") >>= aRelativeSize)
912 aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ));
913 aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ));
914 bCustom = true;
916 else
918 eExpansion = css::chart::ChartLegendExpansion_HIGH;
921 xLegendProp->getPropertyValue("AnchorPosition") >>= eLegendPosition;
922 lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize );
925 // create entries
926 double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo
927 // #i109336# Improve auto positioning in chart
928 sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
929 sal_Int32 nSymbolWidth = nSymbolHeight;
931 for (LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
933 if (pLegendEntryProvider)
935 awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
936 sal_Int32 nCurrentWidth = aCurrentRatio.Width;
937 if( aCurrentRatio.Height > 0 )
939 nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
941 nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
944 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
946 std::vector<ViewLegendEntry> aViewEntries;
947 for(LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
949 if (pLegendEntryProvider)
951 std::vector<ViewLegendEntry> aNewEntries = pLegendEntryProvider->createLegendEntries(
952 aMaxSymbolExtent, eExpansion, xLegendProp,
953 xLegendContainer, m_xShapeFactory, m_xContext, mrModel);
954 aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
958 bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode );
960 uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider( mrModel.getDataProvider(), uno::UNO_QUERY );
961 bool bIsPivotChart = xPivotTableDataProvider.is();
963 if ( !aViewEntries.empty() || bIsPivotChart )
965 // create buttons
966 long nUsedButtonHeight = 0;
967 bool bPlaceButtonsVertically = (eLegendPosition != LegendPosition_PAGE_START &&
968 eLegendPosition != LegendPosition_PAGE_END &&
969 eExpansion != css::chart::ChartLegendExpansion_WIDE);
971 std::vector<std::shared_ptr<VButton>> aButtons = lcl_createButtons(xLegendContainer, m_xShapeFactory, mrModel, bPlaceButtonsVertically, nUsedButtonHeight);
973 // A custom size includes the size we used for buttons already, so we need to
974 // subtract that from the size that is available for the legend
975 if (bCustom)
976 aLegendSize.Height -= nUsedButtonHeight;
978 // place the legend entries
979 aLegendSize = lcl_placeLegendEntries(aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize,
980 aMaxSymbolExtent, aTextProperties, xLegendContainer,
981 m_xShapeFactory, aLegendSize, nUsedButtonHeight, rPageSize, bIsPivotChart);
983 uno::Reference<beans::XPropertySet> xModelPage(mrModel.getPageBackground());
985 for (std::shared_ptr<VButton> const & pButton : aButtons)
987 // adjust the width of the buttons if we place them vertically
988 if (bPlaceButtonsVertically)
989 pButton->setSize({aLegendSize.Width - 200, pButton->getSize().Height});
991 // create the buttons
992 pButton->createShapes(xModelPage);
996 Reference< drawing::XShape > xBorder =
997 pShapeFactory->createRectangle( xLegendContainer,
998 aLegendSize,
999 awt::Point(0,0),
1000 aLineFillProperties.first,
1001 aLineFillProperties.second, ShapeFactory::StackPosition::Bottom );
1003 //because of this name this border will be used for marking the legend
1004 ShapeFactory::setShapeName( xBorder, "MarkHandles" );
1007 catch( const uno::Exception & )
1009 DBG_UNHANDLED_EXCEPTION("chart2" );
1013 void VLegend::changePosition(
1014 awt::Rectangle & rOutAvailableSpace,
1015 const awt::Size & rPageSize )
1017 if(! m_xShape.is())
1018 return;
1022 // determine position and alignment depending on default position
1023 awt::Size aLegendSize = m_xShape->getSize();
1024 Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW );
1025 chart2::RelativePosition aRelativePosition;
1027 bool bAutoPosition =
1028 ! (xLegendProp->getPropertyValue( "RelativePosition") >>= aRelativePosition);
1030 LegendPosition ePos = LegendPosition_CUSTOM;
1031 xLegendProp->getPropertyValue( "AnchorPosition") >>= ePos;
1033 //calculate position
1034 if( bAutoPosition )
1036 // auto position: relative to remaining space
1037 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1038 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1039 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1040 m_xShape->setPosition( aPos );
1042 else
1044 // manual position: relative to whole page
1045 awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height );
1046 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1047 aAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1048 m_xShape->setPosition( aPos );
1050 if( ePos != LegendPosition_CUSTOM )
1052 // calculate remaining space as if having autoposition:
1053 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1054 lcl_calculatePositionAndRemainingSpace(
1055 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1059 catch( const uno::Exception & )
1061 DBG_UNHANDLED_EXCEPTION("chart2" );
1065 } //namespace chart
1067 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */