1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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
;
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
;
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");
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
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
)
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
170 aLabelString
+= aLabelSeq
[i
]->getString();
171 // workaround for Issue #i67540#
172 if( aLabelString
.isEmpty())
176 Reference
< drawing::XShape
> xEntry
=
177 pShapeFactory
->createText( xTarget
, aLabelString
,
178 rTextProperties
.first
, rTextProperties
.second
, uno::Any() );
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");
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
;
211 rColumnWidths
.push_back( nWidth
);
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
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
);
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
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
,
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.
279 aResultingLegendSize
= awt::Size((rPageSize
.Width
* 13) / 80, (rPageSize
.Height
* 31) / 90);
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");
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
] );
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
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
347 if( nCurrentColumn
< nCurrentColumnCount
)
348 aColumnWidths
[nCurrentColumn
] = std::max( nNewWidth
, aColumnWidths
[nCurrentColumn
] );
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
365 //not enough space for the current amount of columns
366 //try again with less columns
367 nMaxColumnCount
= nCurrentColumnCount
-1;
371 aColumnWidths
.clear();
376 //add a new row and try the same entry again
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
);
413 nSumHeight
-= aRowHeights
[nRow
];
414 aRowHeights
.pop_back();
415 nRemainingSpace
= rRemainingSpace
.Height
- nSumHeight
;
416 if( nRemainingSpace
>=0 )
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);
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);
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
466 nNumberOfColumns
= nMaxNumberOfRows
467 ? static_cast< sal_Int32
>(
468 ceil( static_cast< double >( nNumberOfEntries
) /
469 static_cast< double >( nMaxNumberOfRows
) ))
471 nNumberOfRows
= nNumberOfColumns
472 ? static_cast< sal_Int32
>(
473 ceil( static_cast< double >( nNumberOfEntries
) /
474 static_cast< double >( nNumberOfColumns
) ))
477 else if( eExpansion
== css::chart::ChartLegendExpansion_WIDE
)
479 sal_Int32 nMaxNumberOfColumns
= nMaxEntryWidth
480 ? (rRemainingSpace
.Width
- 2*nXPadding
) / nMaxEntryWidth
483 nNumberOfRows
= nMaxNumberOfColumns
484 ? static_cast< sal_Int32
>(
485 ceil( static_cast< double >( nNumberOfEntries
) /
486 static_cast< double >( nMaxNumberOfColumns
) ))
488 nNumberOfColumns
= nNumberOfRows
489 ? static_cast< sal_Int32
>(
490 ceil( static_cast< double >( nNumberOfEntries
) /
491 static_cast< double >( nNumberOfRows
) ))
494 else // css::chart::ChartLegendExpansion_BALANCED
496 double fAspect
= nMaxEntryHeight
497 ? static_cast< double >( nMaxEntryWidth
) / static_cast< double >( nMaxEntryHeight
)
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
) ))
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
)
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
));
545 Reference
< drawing::XShape
> xSymbol( rEntries
[ nEntry
].aSymbol
);
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
;
569 nCurrentXPos
-= aColumnWidths
[nColumn
];
570 if( nColumn
+1 < nNumberOfColumns
)
571 nCurrentXPos
-= nXOffset
;
577 if( bSymbolsLeftSide
)
578 aResultingLegendSize
.Width
= std::max( aResultingLegendSize
.Width
, nCurrentXPos
+ nXPadding
);
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
;
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
);
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
);
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
);
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
);
668 case LegendPosition_CUSTOM
:
670 case LegendPosition::LegendPosition_MAKE_FIXED_SIZE
:
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
,
687 const awt::Size
& aLegendSize
)
689 // calculate position
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();
703 case LegendPosition_LINE_START
:
705 sal_Int32 nExtent
= aLegendSize
.Width
;
706 rRemainingSpace
.Width
-= ( nExtent
+ nXDistance
);
707 rRemainingSpace
.X
+= ( nExtent
+ nXDistance
);
710 case LegendPosition_LINE_END
:
712 rRemainingSpace
.Width
-= ( aLegendSize
.Width
+ nXDistance
);
715 case LegendPosition_PAGE_START
:
717 sal_Int32 nExtent
= aLegendSize
.Height
;
718 rRemainingSpace
.Height
-= ( nExtent
+ nYDistance
);
719 rRemainingSpace
.Y
+= ( nExtent
+ nYDistance
);
722 case LegendPosition_PAGE_END
:
724 rRemainingSpace
.Height
-= ( aLegendSize
.Height
+ nYDistance
);
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 )
741 if( aResult
.Y
+ aLegendSize
.Height
> rPageSize
.Height
)
743 sal_Int32
nNewY( (rPageSize
.Height
- aLegendSize
.Height
) - nEdgeDistance
);
744 if( nNewY
> rPageSize
.Height
/ 4 )
751 bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference
< beans::XPropertySet
>& xLegendProp
, sal_Int16 nDefaultWritingMode
)
753 bool bSymbolsLeftSide
= true;
756 if( SvtLanguageOptions().IsCTLFontEnabled() )
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())
789 if (!xPivotTableDataProvider
->getColumnFields().hasElements())
792 awt::Size
aSize(2000, 700);
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;
818 x
+= aSize
.Width
+ 100;
820 if (bPlaceButtonsVertically
)
821 nUsedHeight
+= y
+ 100;
823 nUsedHeight
+= aSize
.Height
+ 100;
828 } // anonymous namespace
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
,
837 : m_xTarget(xTargetPage
)
838 , m_xShapeFactory(xFactory
)
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
)
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");
871 void VLegend::createShapes(
872 const awt::Size
& rAvailableSpace
,
873 const awt::Size
& rPageSize
)
875 if(! (m_xLegend
.is() &&
876 m_xShapeFactory
.is() &&
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
)),
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
));
918 eExpansion
= css::chart::ChartLegendExpansion_HIGH
;
921 xLegendProp
->getPropertyValue("AnchorPosition") >>= eLegendPosition
;
922 lcl_getProperties( xLegendProp
, aLineFillProperties
, aTextProperties
, rPageSize
);
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
)
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
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
,
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
)
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
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
);
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" );
1067 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */