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"
23 #include <PropertyMapper.hxx>
24 #include <ChartModel.hxx>
25 #include <ObjectIdentifier.hxx>
26 #include <FormattedString.hxx>
27 #include <RelativePositionHelper.hxx>
28 #include <ShapeFactory.hxx>
29 #include <RelativeSizeHelper.hxx>
30 #include <LegendEntryProvider.hxx>
31 #include <chartview/DrawModelWrapper.hxx>
32 #include <com/sun/star/text/WritingMode2.hpp>
33 #include <com/sun/star/beans/XPropertySet.hpp>
34 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
35 #include <com/sun/star/drawing/LineJoint.hpp>
36 #include <com/sun/star/chart/ChartLegendExpansion.hpp>
37 #include <com/sun/star/chart2/LegendPosition.hpp>
38 #include <com/sun/star/chart2/RelativePosition.hpp>
39 #include <com/sun/star/chart2/RelativeSize.hpp>
40 #include <com/sun/star/chart2/XFormattedString2.hpp>
41 #include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
42 #include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp>
43 #include <rtl/math.hxx>
44 #include <svl/ctloptions.hxx>
45 #include <comphelper/diagnose_ex.hxx>
46 #include <tools/UnitConversion.hxx>
52 using namespace ::com::sun::star
;
53 using namespace ::com::sun::star::chart2
;
55 using ::com::sun::star::uno::Reference
;
56 using ::com::sun::star::uno::Sequence
;
64 typedef std::pair
< ::chart::tNameSequence
, ::chart::tAnySequence
> tPropertyValues
;
66 double lcl_CalcViewFontSize(
67 const Reference
< beans::XPropertySet
> & xProp
,
68 const awt::Size
& rReferenceSize
)
70 double fResult
= 10.0;
72 float fFontHeight( 0.0 );
73 if( xProp
.is() && ( xProp
->getPropertyValue( u
"CharHeight"_ustr
) >>= fFontHeight
))
75 fResult
= fFontHeight
;
78 awt::Size aPropRefSize
;
79 if( (xProp
->getPropertyValue( u
"ReferencePageSize"_ustr
) >>= aPropRefSize
) &&
80 (aPropRefSize
.Height
> 0))
82 fResult
= ::chart::RelativeSizeHelper::calculate( fFontHeight
, aPropRefSize
, rReferenceSize
);
85 catch( const uno::Exception
& )
87 DBG_UNHANDLED_EXCEPTION("chart2");
91 return convertPointToMm100(fResult
);
94 void lcl_getProperties(
95 const Reference
< beans::XPropertySet
> & xLegendProp
,
96 tPropertyValues
& rOutLineFillProperties
,
97 tPropertyValues
& rOutTextProperties
,
98 const awt::Size
& rReferenceSize
)
100 // Get Line- and FillProperties from model legend
101 if( !xLegendProp
.is())
104 // set rOutLineFillProperties
105 ::chart::tPropertyNameValueMap aLineFillValueMap
;
106 ::chart::PropertyMapper::getValueMap( aLineFillValueMap
, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp
);
108 aLineFillValueMap
[ u
"LineJoint"_ustr
] <<= drawing::LineJoint_ROUND
;
110 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
111 rOutLineFillProperties
.first
, rOutLineFillProperties
.second
, aLineFillValueMap
);
113 // set rOutTextProperties
114 ::chart::tPropertyNameValueMap aTextValueMap
;
115 ::chart::PropertyMapper::getValueMap( aTextValueMap
, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp
);
117 aTextValueMap
[ u
"TextAutoGrowHeight"_ustr
] <<= true;
118 aTextValueMap
[ u
"TextAutoGrowWidth"_ustr
] <<= true;
119 aTextValueMap
[ u
"TextHorizontalAdjust"_ustr
] <<= drawing::TextHorizontalAdjust_LEFT
;
120 aTextValueMap
[ u
"TextMaximumFrameWidth"_ustr
] <<= rReferenceSize
.Width
; //needs to be overwritten by actual available space in the legend
122 // recalculate font size
123 awt::Size aPropRefSize
;
124 float fFontHeight( 0.0 );
125 if( (xLegendProp
->getPropertyValue( u
"ReferencePageSize"_ustr
) >>= aPropRefSize
) &&
126 (aPropRefSize
.Height
> 0) &&
127 (aTextValueMap
[ u
"CharHeight"_ustr
] >>= fFontHeight
) )
129 aTextValueMap
[ u
"CharHeight"_ustr
] <<=
130 static_cast< float >(
131 ::chart::RelativeSizeHelper::calculate( fFontHeight
, aPropRefSize
, rReferenceSize
));
133 if( aTextValueMap
[ u
"CharHeightAsian"_ustr
] >>= fFontHeight
)
135 aTextValueMap
[ u
"CharHeightAsian"_ustr
] <<=
136 static_cast< float >(
137 ::chart::RelativeSizeHelper::calculate( fFontHeight
, aPropRefSize
, rReferenceSize
));
139 if( aTextValueMap
[ u
"CharHeightComplex"_ustr
] >>= fFontHeight
)
141 aTextValueMap
[ u
"CharHeightComplex"_ustr
] <<=
142 static_cast< float >(
143 ::chart::RelativeSizeHelper::calculate( fFontHeight
, aPropRefSize
, rReferenceSize
));
147 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
148 rOutTextProperties
.first
, rOutTextProperties
.second
, aTextValueMap
);
151 awt::Size
lcl_createTextShapes(
152 const std::vector
<ViewLegendEntry
> & rEntries
,
153 const rtl::Reference
<SvxShapeGroupAnyD
> & xTarget
,
154 std::vector
< rtl::Reference
<SvxShapeText
> > & rOutTextShapes
,
155 const tPropertyValues
& rTextProperties
)
159 for (ViewLegendEntry
const & rEntry
: rEntries
)
163 OUString aLabelString
;
166 // tdf#150034 limit legend label text
167 if (rEntry
.xLabel
->getString().getLength() > 520)
169 sal_Int32 nIndex
= rEntry
.xLabel
->getString().indexOf(' ', 500);
170 rEntry
.xLabel
->setString(
171 rEntry
.xLabel
->getString().copy(0, nIndex
> 500 ? nIndex
: 500));
174 aLabelString
+= rEntry
.xLabel
->getString();
175 // workaround for Issue #i67540#
176 if( aLabelString
.isEmpty())
180 rtl::Reference
<SvxShapeText
> xEntry
=
181 ShapeFactory::createText( xTarget
, aLabelString
,
182 rTextProperties
.first
, rTextProperties
.second
, uno::Any() );
185 awt::Size
aCurrSize( xEntry
->getSize());
186 aResult
.Width
= std::max( aResult
.Width
, aCurrSize
.Width
);
187 aResult
.Height
= std::max( aResult
.Height
, aCurrSize
.Height
);
189 rOutTextShapes
.push_back( xEntry
);
191 catch( const uno::Exception
& )
193 DBG_UNHANDLED_EXCEPTION("chart2");
200 void lcl_collectColumnWidths( std::vector
< sal_Int32
>& rColumnWidths
, const sal_Int32 nNumberOfRows
, const sal_Int32 nNumberOfColumns
,
201 const std::vector
< rtl::Reference
<SvxShapeText
> >& rTextShapes
, sal_Int32 nSymbolPlusDistanceWidth
)
203 rColumnWidths
.clear();
204 sal_Int32 nNumberOfEntries
= rTextShapes
.size();
205 for (sal_Int32 nRow
= 0; nRow
< nNumberOfRows
; ++nRow
)
207 for (sal_Int32 nColumn
= 0; nColumn
< nNumberOfColumns
; ++nColumn
)
209 sal_Int32 nEntry
= nColumn
+ nRow
* nNumberOfColumns
;
210 if( nEntry
< nNumberOfEntries
)
212 awt::Size
aTextSize( rTextShapes
[ nEntry
]->getSize() );
213 sal_Int32 nWidth
= nSymbolPlusDistanceWidth
+ aTextSize
.Width
;
215 rColumnWidths
.push_back( nWidth
);
217 rColumnWidths
[nColumn
] = std::max( nWidth
, rColumnWidths
[nColumn
] );
223 void lcl_collectRowHeighs( std::vector
< sal_Int32
>& rRowHeights
, const sal_Int32 nNumberOfRows
, const sal_Int32 nNumberOfColumns
,
224 const std::vector
< rtl::Reference
<SvxShapeText
> >& rTextShapes
)
226 // calculate maximum height for each row
227 // and collect column widths
229 sal_Int32 nNumberOfEntries
= rTextShapes
.size();
230 for (sal_Int32 nRow
= 0; nRow
< nNumberOfRows
; ++nRow
)
232 sal_Int32 nCurrentRowHeight
= 0;
233 for (sal_Int32 nColumn
= 0; nColumn
< nNumberOfColumns
; ++nColumn
)
235 sal_Int32 nEntry
= nColumn
+ nRow
* nNumberOfColumns
;
236 if( nEntry
< nNumberOfEntries
)
238 awt::Size
aTextSize( rTextShapes
[ nEntry
]->getSize() );
239 nCurrentRowHeight
= std::max( nCurrentRowHeight
, aTextSize
.Height
);
242 rRowHeights
.push_back( nCurrentRowHeight
);
246 sal_Int32
lcl_getTextLineHeight( const std::vector
< sal_Int32
>& aRowHeights
, const sal_Int32 nNumberOfRows
, double fViewFontSize
)
248 const sal_Int32 nFontHeight
= static_cast< sal_Int32
>( fViewFontSize
);
251 sal_Int32 nTextLineHeight
= nFontHeight
;
252 for (sal_Int32 nRow
= 0; nRow
< nNumberOfRows
; ++nRow
)
254 sal_Int32 nFullTextHeight
= aRowHeights
[nRow
];
255 if( ( nFullTextHeight
/ nFontHeight
) <= 1 )
257 nTextLineHeight
= nFullTextHeight
;//found an entry with one line-> have real text height
261 return nTextLineHeight
;
264 //returns resulting legend size
265 awt::Size
lcl_placeLegendEntries(
266 std::vector
<ViewLegendEntry
> & rEntries
,
267 css::chart::ChartLegendExpansion eExpansion
,
268 bool bSymbolsLeftSide
,
269 double fViewFontSize
,
270 const awt::Size
& rMaxSymbolExtent
,
271 tPropertyValues
& rTextProperties
,
272 const rtl::Reference
<SvxShapeGroupAnyD
> & xTarget
,
273 const awt::Size
& rRemainingSpace
,
274 sal_Int32 nYStartPosition
,
275 const awt::Size
& rPageSize
,
277 awt::Size
& rDefaultLegendSize
)
279 bool bIsCustomSize
= (eExpansion
== css::chart::ChartLegendExpansion_CUSTOM
);
280 awt::Size
aResultingLegendSize(0,0);
281 // For Pivot charts set the *minimum* legend size as a function of page size.
283 aResultingLegendSize
= awt::Size((rPageSize
.Width
* 13) / 80, (rPageSize
.Height
* 31) / 90);
285 aResultingLegendSize
= awt::Size(rRemainingSpace
.Width
, rRemainingSpace
.Height
+ nYStartPosition
);
287 // #i109336# Improve auto positioning in chart
288 sal_Int32 nXPadding
= static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.33 ) );
289 sal_Int32 nXOffset
= static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.66 ) );
290 sal_Int32 nYPadding
= static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.2 ) );
291 sal_Int32 nYOffset
= static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.2 ) );
293 const sal_Int32 nSymbolToTextDistance
= static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.22 ) );//minimum 1mm
294 const sal_Int32 nSymbolPlusDistanceWidth
= rMaxSymbolExtent
.Width
+ nSymbolToTextDistance
;
295 sal_Int32 nMaxTextWidth
= rRemainingSpace
.Width
- nSymbolPlusDistanceWidth
;
296 uno::Any
* pFrameWidthAny
= PropertyMapper::getValuePointer( rTextProperties
.second
, rTextProperties
.first
, u
"TextMaximumFrameWidth");
299 if( eExpansion
== css::chart::ChartLegendExpansion_HIGH
)
301 // limit the width of texts to 30% of the total available width
302 // #i109336# Improve auto positioning in chart
303 nMaxTextWidth
= rRemainingSpace
.Width
* 3 / 10;
305 *pFrameWidthAny
<<= nMaxTextWidth
;
308 std::vector
< rtl::Reference
<SvxShapeText
> > aTextShapes
;
309 awt::Size aMaxEntryExtent
= lcl_createTextShapes( rEntries
, xTarget
, aTextShapes
, rTextProperties
);
310 OSL_ASSERT( aTextShapes
.size() == rEntries
.size());
312 sal_Int32 nMaxEntryWidth
= nXOffset
+ nSymbolPlusDistanceWidth
+ aMaxEntryExtent
.Width
;
313 sal_Int32 nMaxEntryHeight
= nYOffset
+ aMaxEntryExtent
.Height
;
314 sal_Int32 nNumberOfEntries
= rEntries
.size();
316 rDefaultLegendSize
.Width
= nMaxEntryWidth
;
317 rDefaultLegendSize
.Height
= nMaxEntryHeight
+ nYPadding
;
319 sal_Int32 nNumberOfColumns
= 0, nNumberOfRows
= 0;
320 std::vector
< sal_Int32
> aColumnWidths
;
321 std::vector
< sal_Int32
> aRowHeights
;
323 sal_Int32 nTextLineHeight
= static_cast< sal_Int32
>( fViewFontSize
);
325 // determine layout depending on LegendExpansion
326 if( eExpansion
== css::chart::ChartLegendExpansion_CUSTOM
)
328 sal_Int32 nCurrentRow
=0;
329 sal_Int32 nCurrentColumn
=-1;
330 sal_Int32 nMaxColumnCount
=-1;
331 for( sal_Int32 nN
=0; nN
<static_cast<sal_Int32
>(aTextShapes
.size()); nN
++ )
333 const rtl::Reference
<SvxShapeText
>& xShape( aTextShapes
[nN
] );
336 awt::Size
aSize( xShape
->getSize() );
337 sal_Int32 nNewWidth
= aSize
.Width
+ nSymbolPlusDistanceWidth
;
338 sal_Int32 nCurrentColumnCount
= aColumnWidths
.size();
340 //are we allowed to add a new column?
341 if( nMaxColumnCount
==-1 || (nCurrentColumn
+1) < nMaxColumnCount
)
343 //try add a new column
345 if( nCurrentColumn
< nCurrentColumnCount
)
347 //check whether the current column width is sufficient for the new entry
348 if( aColumnWidths
[nCurrentColumn
]>=nNewWidth
)
350 //all good proceed with next entry
354 aColumnWidths
[nCurrentColumn
] = std::max( nNewWidth
, aColumnWidths
[nCurrentColumn
] );
356 aColumnWidths
.push_back(nNewWidth
);
358 //do the columns still fit into the given size?
359 nCurrentColumnCount
= aColumnWidths
.size();//update count
360 sal_Int32 nSumWidth
= 0;
361 for (sal_Int32 nColumn
= 0; nColumn
< nCurrentColumnCount
; nColumn
++)
362 nSumWidth
+= aColumnWidths
[nColumn
];
364 if( nSumWidth
<= rRemainingSpace
.Width
|| nCurrentColumnCount
==1 )
366 //all good proceed with next entry
371 //not enough space for the current amount of columns
372 //try again with less columns
373 nMaxColumnCount
= nCurrentColumnCount
-1;
377 aColumnWidths
.clear();
382 //add a new row and try the same entry again
388 nNumberOfColumns
= aColumnWidths
.size();
389 nNumberOfRows
= nCurrentRow
+1;
391 //check if there is not enough space so that some entries must be removed
392 lcl_collectRowHeighs( aRowHeights
, nNumberOfRows
, nNumberOfColumns
, aTextShapes
);
393 nTextLineHeight
= lcl_getTextLineHeight( aRowHeights
, nNumberOfRows
, fViewFontSize
);
394 sal_Int32 nSumHeight
= 0;
395 for (sal_Int32 nRow
=0; nRow
< nNumberOfRows
; nRow
++)
396 nSumHeight
+= aRowHeights
[nRow
];
397 sal_Int32 nRemainingSpace
= rRemainingSpace
.Height
- nSumHeight
;
399 if( nRemainingSpace
< -100 ) // 1mm tolerance for OOXML interop tdf#90404
401 //remove entries that are too big
402 for (sal_Int32 nRow
= nNumberOfRows
; nRow
--; )
404 for (sal_Int32 nColumn
= nNumberOfColumns
; nColumn
--; )
406 sal_Int32 nEntry
= nColumn
+ nRow
* nNumberOfColumns
;
407 if( nEntry
< static_cast<sal_Int32
>(aTextShapes
.size()) )
409 DrawModelWrapper::removeShape( aTextShapes
[nEntry
] );
410 aTextShapes
.pop_back();
412 if( nEntry
< nNumberOfEntries
&& ( nEntry
!= 0 || nNumberOfColumns
!= 1 ) )
414 DrawModelWrapper::removeShape( rEntries
[ nEntry
].xSymbol
);
419 if (nRow
== 0 && nNumberOfColumns
== 1)
423 OUString aLabelString
= rEntries
[0].xLabel
->getString();
424 static constexpr OUString sDots
= u
"..."_ustr
;
425 for (sal_Int32 nNewLen
= aLabelString
.getLength() - sDots
.getLength(); nNewLen
> 0; )
427 OUString aNewLabel
= aLabelString
.subView(0, nNewLen
) + sDots
;
428 rtl::Reference
<SvxShapeText
> xEntry
= ShapeFactory::createText(
429 xTarget
, aNewLabel
, rTextProperties
.first
, rTextProperties
.second
, uno::Any());
430 nSumHeight
= xEntry
->getSize().Height
;
431 nRemainingSpace
= rRemainingSpace
.Height
- nSumHeight
;
432 if (nRemainingSpace
>= 0)
434 sal_Int32 nWidth
= xEntry
->getSize().Width
+ nSymbolPlusDistanceWidth
;
435 if (rRemainingSpace
.Width
- nWidth
>= 0)
437 aTextShapes
.push_back(xEntry
);
438 rEntries
[0].xLabel
->setString(aNewLabel
);
439 aRowHeights
[0] = nSumHeight
;
440 aColumnWidths
[0] = nWidth
;
444 DrawModelWrapper::removeShape(xEntry
);
445 // The intention here is to make pathological cases with extremely large labels
446 // converge a little faster
447 if (nNewLen
> 10 && std::abs(nRemainingSpace
) > nSumHeight
/ 10)
448 nNewLen
-= nNewLen
/ 10;
452 if (aTextShapes
.empty())
454 DrawModelWrapper::removeShape(rEntries
[0].xSymbol
);
457 aRowHeights
.pop_back();
460 catch (const uno::Exception
&)
462 DBG_UNHANDLED_EXCEPTION("chart2");
467 nSumHeight
-= aRowHeights
[nRow
];
468 aRowHeights
.pop_back();
469 nRemainingSpace
= rRemainingSpace
.Height
- nSumHeight
;
470 if (nRemainingSpace
>= 0)
474 nNumberOfRows
= static_cast<sal_Int32
>(aRowHeights
.size());
476 if( nRemainingSpace
>= -100 ) // 1mm tolerance for OOXML interop tdf#90404
478 sal_Int32 nNormalSpacingHeight
= 2*nYPadding
+(nNumberOfRows
-1)*nYOffset
;
479 if( nRemainingSpace
< nNormalSpacingHeight
)
481 //reduce spacing between the entries
482 nYPadding
= nYOffset
= nRemainingSpace
/(nNumberOfRows
+1);
486 //we have some space left that should be spread equally between all rows
487 sal_Int32 nRemainingSingleSpace
= (nRemainingSpace
-nNormalSpacingHeight
)/(nNumberOfRows
+1);
488 nYPadding
+= nRemainingSingleSpace
;
489 nYOffset
+= nRemainingSingleSpace
;
493 //check spacing between columns
494 sal_Int32 nSumWidth
= 0;
495 for (sal_Int32 nColumn
= 0; nColumn
< nNumberOfColumns
; nColumn
++)
496 nSumWidth
+= aColumnWidths
[nColumn
];
497 nRemainingSpace
= rRemainingSpace
.Width
- nSumWidth
;
498 if( nRemainingSpace
>=0 )
500 sal_Int32 nNormalSpacingWidth
= 2*nXPadding
+(nNumberOfColumns
-1)*nXOffset
;
501 if( nRemainingSpace
< nNormalSpacingWidth
)
503 //reduce spacing between the entries
504 nXPadding
= nXOffset
= nRemainingSpace
/(nNumberOfColumns
+1);
508 //we have some space left that should be spread equally between all columns
509 sal_Int32 nRemainingSingleSpace
= (nRemainingSpace
-nNormalSpacingWidth
)/(nNumberOfColumns
+1);
510 nXPadding
+= nRemainingSingleSpace
;
511 nXOffset
+= nRemainingSingleSpace
;
515 else if( eExpansion
== css::chart::ChartLegendExpansion_HIGH
)
517 sal_Int32 nMaxNumberOfRows
= nMaxEntryHeight
518 ? (rRemainingSpace
.Height
- 2*nYPadding
) / nMaxEntryHeight
521 nNumberOfColumns
= nMaxNumberOfRows
522 ? static_cast< sal_Int32
>(
523 ceil( static_cast< double >( nNumberOfEntries
) /
524 static_cast< double >( nMaxNumberOfRows
) ))
526 nNumberOfRows
= nNumberOfColumns
527 ? static_cast< sal_Int32
>(
528 ceil( static_cast< double >( nNumberOfEntries
) /
529 static_cast< double >( nNumberOfColumns
) ))
532 else if( eExpansion
== css::chart::ChartLegendExpansion_WIDE
)
534 sal_Int32 nMaxNumberOfColumns
= nMaxEntryWidth
535 ? (rRemainingSpace
.Width
- 2*nXPadding
) / nMaxEntryWidth
538 nNumberOfRows
= nMaxNumberOfColumns
539 ? static_cast< sal_Int32
>(
540 ceil( static_cast< double >( nNumberOfEntries
) /
541 static_cast< double >( nMaxNumberOfColumns
) ))
543 nNumberOfColumns
= nNumberOfRows
544 ? static_cast< sal_Int32
>(
545 ceil( static_cast< double >( nNumberOfEntries
) /
546 static_cast< double >( nNumberOfRows
) ))
549 else // css::chart::ChartLegendExpansion_BALANCED
551 double fAspect
= nMaxEntryHeight
552 ? static_cast< double >( nMaxEntryWidth
) / static_cast< double >( nMaxEntryHeight
)
555 nNumberOfRows
= static_cast< sal_Int32
>(
556 ceil( sqrt( static_cast< double >( nNumberOfEntries
) * fAspect
)));
557 nNumberOfColumns
= nNumberOfRows
558 ? static_cast< sal_Int32
>(
559 ceil( static_cast< double >( nNumberOfEntries
) /
560 static_cast< double >( nNumberOfRows
) ))
565 return aResultingLegendSize
;
567 if( eExpansion
!= css::chart::ChartLegendExpansion_CUSTOM
)
569 lcl_collectColumnWidths( aColumnWidths
, nNumberOfRows
, nNumberOfColumns
, aTextShapes
, nSymbolPlusDistanceWidth
);
570 lcl_collectRowHeighs( aRowHeights
, nNumberOfRows
, nNumberOfColumns
, aTextShapes
);
571 nTextLineHeight
= lcl_getTextLineHeight( aRowHeights
, nNumberOfRows
, fViewFontSize
);
574 sal_Int32 nCurrentXPos
= bSymbolsLeftSide
? nXPadding
: -nXPadding
;
576 // place entries into column and rows
577 sal_Int32 nMaxYPos
= 0;
579 for (sal_Int32 nColumn
= 0; nColumn
< nNumberOfColumns
; ++nColumn
)
581 sal_Int32 nCurrentYPos
= nYPadding
+ nYStartPosition
;
582 for (sal_Int32 nRow
= 0; nRow
< nNumberOfRows
; ++nRow
)
584 sal_Int32 nEntry
= nColumn
+ nRow
* nNumberOfColumns
;
585 if( nEntry
>= nNumberOfEntries
)
589 const rtl::Reference
<SvxShapeText
>& xTextShape( aTextShapes
[nEntry
] );
590 if( xTextShape
.is() )
592 awt::Size
aTextSize( xTextShape
->getSize() );
593 sal_Int32 nTextXPos
= nCurrentXPos
+ nSymbolPlusDistanceWidth
;
594 if( !bSymbolsLeftSide
)
595 nTextXPos
= nCurrentXPos
- nSymbolPlusDistanceWidth
- aTextSize
.Width
;
596 xTextShape
->setPosition( awt::Point( nTextXPos
, nCurrentYPos
));
600 rtl::Reference
<SvxShapeGroup
> & xSymbol( rEntries
[ nEntry
].xSymbol
);
603 awt::Size
aSymbolSize( rMaxSymbolExtent
);
604 sal_Int32 nSymbolXPos
= nCurrentXPos
;
605 if( !bSymbolsLeftSide
)
606 nSymbolXPos
= nCurrentXPos
- rMaxSymbolExtent
.Width
;
607 sal_Int32 nSymbolYPos
= nCurrentYPos
+ ( ( nTextLineHeight
- aSymbolSize
.Height
) / 2 );
608 xSymbol
->setPosition( awt::Point( nSymbolXPos
, nSymbolYPos
) );
611 nCurrentYPos
+= aRowHeights
[ nRow
];
612 if( nRow
+1 < nNumberOfRows
)
613 nCurrentYPos
+= nYOffset
;
614 nMaxYPos
= std::max( nMaxYPos
, nCurrentYPos
);
616 if( bSymbolsLeftSide
)
618 nCurrentXPos
+= aColumnWidths
[nColumn
];
619 if( nColumn
+1 < nNumberOfColumns
)
620 nCurrentXPos
+= nXOffset
;
624 nCurrentXPos
-= aColumnWidths
[nColumn
];
625 if( nColumn
+1 < nNumberOfColumns
)
626 nCurrentXPos
-= nXOffset
;
632 if( bSymbolsLeftSide
)
633 aResultingLegendSize
.Width
= std::max( aResultingLegendSize
.Width
, nCurrentXPos
+ nXPadding
);
636 sal_Int32 nLegendWidth
= -(nCurrentXPos
-nXPadding
);
637 aResultingLegendSize
.Width
= std::max( aResultingLegendSize
.Width
, nLegendWidth
);
639 aResultingLegendSize
.Height
= std::max( aResultingLegendSize
.Height
, nMaxYPos
+ nYPadding
);
642 if( !bSymbolsLeftSide
)
644 sal_Int32 nLegendWidth
= aResultingLegendSize
.Width
;
645 awt::Point
aPos(0,0);
646 for( sal_Int32 nEntry
=0; nEntry
<nNumberOfEntries
; nEntry
++ )
648 rtl::Reference
<SvxShapeGroup
> & xSymbol( rEntries
[ nEntry
].xSymbol
);
649 aPos
= xSymbol
->getPosition();
650 aPos
.X
+= nLegendWidth
;
651 xSymbol
->setPosition( aPos
);
652 rtl::Reference
<SvxShapeText
> & xText( aTextShapes
[ nEntry
] );
653 aPos
= xText
->getPosition();
654 aPos
.X
+= nLegendWidth
;
655 xText
->setPosition( aPos
);
659 return aResultingLegendSize
;
662 // #i109336# Improve auto positioning in chart
663 sal_Int32
lcl_getLegendLeftRightMargin()
665 return 210; // 1/100 mm
668 // #i109336# Improve auto positioning in chart
669 sal_Int32
lcl_getLegendTopBottomMargin()
671 return 185; // 1/100 mm
674 chart2::RelativePosition
lcl_getDefaultPosition( LegendPosition ePos
, const awt::Rectangle
& rOutAvailableSpace
, const awt::Size
& rPageSize
)
676 chart2::RelativePosition aResult
;
680 case LegendPosition_LINE_START
:
682 // #i109336# Improve auto positioning in chart
683 const double fDefaultDistance
= static_cast< double >( lcl_getLegendLeftRightMargin() ) /
684 static_cast< double >( rPageSize
.Width
);
685 aResult
= chart2::RelativePosition(
686 fDefaultDistance
, 0.5, drawing::Alignment_LEFT
);
689 case LegendPosition_LINE_END
:
691 // #i109336# Improve auto positioning in chart
692 const double fDefaultDistance
= static_cast< double >( lcl_getLegendLeftRightMargin() ) /
693 static_cast< double >( rPageSize
.Width
);
694 aResult
= chart2::RelativePosition(
695 1.0 - fDefaultDistance
, 0.5, drawing::Alignment_RIGHT
);
698 case LegendPosition_PAGE_START
:
700 // #i109336# Improve auto positioning in chart
701 const double fDefaultDistance
= static_cast< double >( lcl_getLegendTopBottomMargin() ) /
702 static_cast< double >( rPageSize
.Height
);
703 double fDistance
= (static_cast<double>(rOutAvailableSpace
.Y
)/static_cast<double>(rPageSize
.Height
)) + fDefaultDistance
;
704 aResult
= chart2::RelativePosition(
705 0.5, fDistance
, drawing::Alignment_TOP
);
708 case LegendPosition_PAGE_END
:
710 // #i109336# Improve auto positioning in chart
711 const double fDefaultDistance
= static_cast< double >( lcl_getLegendTopBottomMargin() ) /
712 static_cast< double >( rPageSize
.Height
);
714 double fDistance
= double(rPageSize
.Height
- (rOutAvailableSpace
.Y
+ rOutAvailableSpace
.Height
));
715 fDistance
+= fDefaultDistance
;
716 fDistance
/= double(rPageSize
.Height
);
718 aResult
= chart2::RelativePosition(
719 0.5, 1.0 - fDistance
, drawing::Alignment_BOTTOM
);
722 case LegendPosition::LegendPosition_MAKE_FIXED_SIZE
:
732 a point relative to the upper left corner that can be used for
733 XShape::setPosition()
735 awt::Point
lcl_calculatePositionAndRemainingSpace(
736 awt::Rectangle
& rRemainingSpace
,
737 const awt::Size
& rPageSize
,
738 const chart2::RelativePosition
& rRelPos
,
740 const awt::Size
& aLegendSize
,
743 // calculate position
745 static_cast< sal_Int32
>( rRelPos
.Primary
* rPageSize
.Width
),
746 static_cast< sal_Int32
>( rRelPos
.Secondary
* rPageSize
.Height
));
748 aResult
= RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
749 aResult
, aLegendSize
, rRelPos
.Anchor
);
751 // adapt rRemainingSpace if LegendPosition is not CUSTOM
752 // #i109336# Improve auto positioning in chart
753 sal_Int32 nXDistance
= lcl_getLegendLeftRightMargin();
754 sal_Int32 nYDistance
= lcl_getLegendTopBottomMargin();
755 if (!bOverlay
) switch( ePos
)
757 case LegendPosition_LINE_START
:
759 sal_Int32 nExtent
= aLegendSize
.Width
;
760 rRemainingSpace
.Width
-= ( nExtent
+ nXDistance
);
761 rRemainingSpace
.X
+= ( nExtent
+ nXDistance
);
764 case LegendPosition_LINE_END
:
766 rRemainingSpace
.Width
-= ( aLegendSize
.Width
+ nXDistance
);
769 case LegendPosition_PAGE_START
:
771 sal_Int32 nExtent
= aLegendSize
.Height
;
772 rRemainingSpace
.Height
-= ( nExtent
+ nYDistance
);
773 rRemainingSpace
.Y
+= ( nExtent
+ nYDistance
);
776 case LegendPosition_PAGE_END
:
778 rRemainingSpace
.Height
-= ( aLegendSize
.Height
+ nYDistance
);
787 // adjust the legend position. Esp. for old files that had slightly smaller legends
788 const sal_Int32
nEdgeDistance( 30 );
789 if( aResult
.X
+ aLegendSize
.Width
> rPageSize
.Width
)
791 sal_Int32
nNewX( (rPageSize
.Width
- aLegendSize
.Width
) - nEdgeDistance
);
792 if( nNewX
> rPageSize
.Width
/ 4 )
795 if( aResult
.Y
+ aLegendSize
.Height
> rPageSize
.Height
)
797 sal_Int32
nNewY( (rPageSize
.Height
- aLegendSize
.Height
) - nEdgeDistance
);
798 if( nNewY
> rPageSize
.Height
/ 4 )
805 bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference
< beans::XPropertySet
>& xLegendProp
, sal_Int16 nDefaultWritingMode
)
807 bool bSymbolsLeftSide
= true;
810 if( SvtCTLOptions::IsCTLFontEnabled() )
814 sal_Int16 nWritingMode
=-1;
815 if( xLegendProp
->getPropertyValue( u
"WritingMode"_ustr
) >>= nWritingMode
)
817 if( nWritingMode
== text::WritingMode2::PAGE
)
818 nWritingMode
= nDefaultWritingMode
;
819 if( nWritingMode
== text::WritingMode2::RL_TB
)
820 bSymbolsLeftSide
=false;
825 catch( const uno::Exception
& )
827 DBG_UNHANDLED_EXCEPTION("chart2");
829 return bSymbolsLeftSide
;
832 std::vector
<std::shared_ptr
<VButton
>> lcl_createButtons(
833 rtl::Reference
<SvxShapeGroupAnyD
> const & xLegendContainer
,
834 ChartModel
& rModel
, bool bPlaceButtonsVertically
, tools::Long
& nUsedHeight
)
836 std::vector
<std::shared_ptr
<VButton
>> aButtons
;
838 uno::Reference
<chart2::data::XPivotTableDataProvider
> xPivotTableDataProvider(rModel
.getDataProvider(), uno::UNO_QUERY
);
839 if (!xPivotTableDataProvider
.is())
842 if (!xPivotTableDataProvider
->getColumnFields().hasElements())
845 awt::Size
aSize(2000, 700);
849 const css::uno::Sequence
<chart2::data::PivotTableFieldEntry
> aPivotFieldEntries
= xPivotTableDataProvider
->getColumnFields();
850 for (chart2::data::PivotTableFieldEntry
const & sColumnFieldEntry
: aPivotFieldEntries
)
852 auto pButton
= std::make_shared
<VButton
>();
853 aButtons
.push_back(pButton
);
854 pButton
->init(xLegendContainer
);
855 awt::Point
aNewPosition(x
, y
);
856 pButton
->setLabel(sColumnFieldEntry
.Name
);
857 pButton
->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry
.DimensionIndex
));
858 pButton
->setPosition(aNewPosition
);
859 pButton
->setSize(aSize
);
860 if (sColumnFieldEntry
.Name
== "Data")
862 pButton
->showArrow(false);
863 pButton
->setBGColor(Color(0x00F6F6F6));
865 if (sColumnFieldEntry
.HasHiddenMembers
)
866 pButton
->setArrowColor(Color(0x0000FF));
868 if (bPlaceButtonsVertically
)
869 y
+= aSize
.Height
+ 100;
871 x
+= aSize
.Width
+ 100;
873 if (bPlaceButtonsVertically
)
874 nUsedHeight
+= y
+ 100;
876 nUsedHeight
+= aSize
.Height
+ 100;
881 } // anonymous namespace
884 rtl::Reference
< Legend
> xLegend
,
885 const Reference
< uno::XComponentContext
> & xContext
,
886 std::vector
< LegendEntryProvider
* >&& rLegendEntryProviderList
,
887 rtl::Reference
<SvxShapeGroupAnyD
> xTargetPage
,
889 : m_xTarget(std::move(xTargetPage
))
890 , m_xLegend(std::move(xLegend
))
892 , m_xContext(xContext
)
893 , m_aLegendEntryProviderList(std::move(rLegendEntryProviderList
))
894 , m_nDefaultWritingMode(text::WritingMode2::LR_TB
)
898 void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode
)
900 m_nDefaultWritingMode
= nDefaultWritingMode
;
903 bool VLegend::isVisible( const rtl::Reference
< Legend
> & xLegend
)
911 xLegend
->getPropertyValue( u
"Show"_ustr
) >>= bShow
;
913 catch( const uno::Exception
& )
915 DBG_UNHANDLED_EXCEPTION("chart2");
921 void VLegend::createShapes(
922 const awt::Size
& rAvailableSpace
,
923 const awt::Size
& rPageSize
,
924 awt::Size
& rDefaultLegendSize
)
926 if(! (m_xLegend
.is() && m_xTarget
.is()))
931 //create shape and add to page
932 OUString
aLegendParticle( ObjectIdentifier::createParticleForLegend( &mrModel
) );
933 m_xShape
= ShapeFactory::createGroup2D( m_xTarget
,
934 ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle
) );
936 // create and insert sub-shapes
937 rtl::Reference
<SvxShapeGroupAnyD
> xLegendContainer
= m_xShape
;
938 if( xLegendContainer
.is() )
940 // for quickly setting properties
941 tPropertyValues aLineFillProperties
;
942 tPropertyValues aTextProperties
;
944 css::chart::ChartLegendExpansion eExpansion
= css::chart::ChartLegendExpansion_HIGH
;
945 awt::Size
aLegendSize( rAvailableSpace
);
947 bool bCustom
= false;
948 LegendPosition eLegendPosition
= LegendPosition_LINE_END
;
949 // get Expansion property
950 m_xLegend
->getPropertyValue(u
"Expansion"_ustr
) >>= eExpansion
;
951 if( eExpansion
== css::chart::ChartLegendExpansion_CUSTOM
)
953 RelativeSize aRelativeSize
;
954 if (m_xLegend
->getPropertyValue(u
"RelativeSize"_ustr
) >>= aRelativeSize
)
956 aLegendSize
.Width
= static_cast<sal_Int32
>(::rtl::math::approxCeil( aRelativeSize
.Primary
* rPageSize
.Width
));
957 aLegendSize
.Height
= static_cast<sal_Int32
>(::rtl::math::approxCeil( aRelativeSize
.Secondary
* rPageSize
.Height
));
962 eExpansion
= css::chart::ChartLegendExpansion_HIGH
;
965 m_xLegend
->getPropertyValue(u
"AnchorPosition"_ustr
) >>= eLegendPosition
;
966 lcl_getProperties( m_xLegend
, aLineFillProperties
, aTextProperties
, rPageSize
);
969 double fViewFontSize
= lcl_CalcViewFontSize( m_xLegend
, rPageSize
);//todo
970 // #i109336# Improve auto positioning in chart
971 sal_Int32 nSymbolHeight
= static_cast< sal_Int32
>( fViewFontSize
* 0.6 );
972 sal_Int32 nSymbolWidth
= nSymbolHeight
;
974 for (LegendEntryProvider
* pLegendEntryProvider
: m_aLegendEntryProviderList
)
976 if (pLegendEntryProvider
)
978 awt::Size aCurrentRatio
= pLegendEntryProvider
->getPreferredLegendKeyAspectRatio();
979 sal_Int32 nCurrentWidth
= aCurrentRatio
.Width
;
980 if( aCurrentRatio
.Height
> 0 )
982 nCurrentWidth
= nSymbolHeight
* aCurrentRatio
.Width
/aCurrentRatio
.Height
;
984 nSymbolWidth
= std::max( nSymbolWidth
, nCurrentWidth
);
987 awt::Size
aMaxSymbolExtent( nSymbolWidth
, nSymbolHeight
);
989 std::vector
<ViewLegendEntry
> aViewEntries
;
990 for(LegendEntryProvider
* pLegendEntryProvider
: m_aLegendEntryProviderList
)
992 if (pLegendEntryProvider
)
994 std::vector
<ViewLegendEntry
> aNewEntries
= pLegendEntryProvider
->createLegendEntries(
995 aMaxSymbolExtent
, eLegendPosition
, m_xLegend
,
996 xLegendContainer
, m_xContext
, mrModel
);
997 aViewEntries
.insert( aViewEntries
.end(), aNewEntries
.begin(), aNewEntries
.end() );
1001 bool bSymbolsLeftSide
= lcl_shouldSymbolsBePlacedOnTheLeftSide( m_xLegend
, m_nDefaultWritingMode
);
1003 uno::Reference
<chart2::data::XPivotTableDataProvider
> xPivotTableDataProvider( mrModel
.getDataProvider(), uno::UNO_QUERY
);
1004 bool bIsPivotChart
= xPivotTableDataProvider
.is();
1006 if ( !aViewEntries
.empty() || bIsPivotChart
)
1009 tools::Long nUsedButtonHeight
= 0;
1010 bool bPlaceButtonsVertically
= (eLegendPosition
!= LegendPosition_PAGE_START
&&
1011 eLegendPosition
!= LegendPosition_PAGE_END
&&
1012 eExpansion
!= css::chart::ChartLegendExpansion_WIDE
);
1014 std::vector
<std::shared_ptr
<VButton
>> aButtons
= lcl_createButtons(xLegendContainer
, mrModel
, bPlaceButtonsVertically
, nUsedButtonHeight
);
1016 // A custom size includes the size we used for buttons already, so we need to
1017 // subtract that from the size that is available for the legend
1019 aLegendSize
.Height
-= nUsedButtonHeight
;
1021 // place the legend entries
1022 aLegendSize
= lcl_placeLegendEntries(aViewEntries
, eExpansion
, bSymbolsLeftSide
, fViewFontSize
,
1023 aMaxSymbolExtent
, aTextProperties
, xLegendContainer
,
1024 aLegendSize
, nUsedButtonHeight
, rPageSize
, bIsPivotChart
, rDefaultLegendSize
);
1026 uno::Reference
<beans::XPropertySet
> xModelPage(mrModel
.getPageBackground());
1028 for (std::shared_ptr
<VButton
> const & pButton
: aButtons
)
1030 // adjust the width of the buttons if we place them vertically
1031 if (bPlaceButtonsVertically
)
1032 pButton
->setSize({aLegendSize
.Width
- 200, pButton
->getSize().Height
});
1034 // create the buttons
1035 pButton
->createShapes(xModelPage
);
1038 rtl::Reference
<SvxShapeRect
> xBorder
= ShapeFactory::createRectangle(
1039 xLegendContainer
, aLegendSize
, awt::Point(0, 0), aLineFillProperties
.first
,
1040 aLineFillProperties
.second
, ShapeFactory::StackPosition::Bottom
);
1042 //because of this name this border will be used for marking the legend
1043 ShapeFactory::setShapeName(xBorder
, u
"MarkHandles"_ustr
);
1047 catch( const uno::Exception
& )
1049 DBG_UNHANDLED_EXCEPTION("chart2" );
1053 void VLegend::changePosition(
1054 awt::Rectangle
& rOutAvailableSpace
,
1055 const awt::Size
& rPageSize
,
1056 const css::awt::Size
& rDefaultLegendSize
)
1063 // determine position and alignment depending on default position
1064 awt::Size aLegendSize
= m_xShape
->getSize();
1065 chart2::RelativePosition aRelativePosition
;
1067 bool bDefaultLegendSize
= rDefaultLegendSize
.Width
!= 0 || rDefaultLegendSize
.Height
!= 0;
1068 bool bAutoPosition
=
1069 ! (m_xLegend
->getPropertyValue( u
"RelativePosition"_ustr
) >>= aRelativePosition
);
1071 LegendPosition ePos
= LegendPosition_LINE_END
;
1072 m_xLegend
->getPropertyValue( u
"AnchorPosition"_ustr
) >>= ePos
;
1074 bool bOverlay
= false;
1075 m_xLegend
->getPropertyValue(u
"Overlay"_ustr
) >>= bOverlay
;
1076 //calculate position
1079 // auto position: relative to remaining space
1080 aRelativePosition
= lcl_getDefaultPosition( ePos
, rOutAvailableSpace
, rPageSize
);
1081 awt::Point aPos
= lcl_calculatePositionAndRemainingSpace(
1082 rOutAvailableSpace
, rPageSize
, aRelativePosition
, ePos
, aLegendSize
, bOverlay
);
1083 m_xShape
->setPosition( aPos
);
1087 // manual position: relative to whole page
1088 awt::Rectangle
aAvailableSpace( 0, 0, rPageSize
.Width
, rPageSize
.Height
);
1089 awt::Point aPos
= lcl_calculatePositionAndRemainingSpace(
1090 aAvailableSpace
, rPageSize
, aRelativePosition
, ePos
, bDefaultLegendSize
? rDefaultLegendSize
: aLegendSize
, bOverlay
);
1091 m_xShape
->setPosition( aPos
);
1095 // calculate remaining space as if having autoposition:
1096 aRelativePosition
= lcl_getDefaultPosition( ePos
, rOutAvailableSpace
, rPageSize
);
1097 lcl_calculatePositionAndRemainingSpace(
1098 rOutAvailableSpace
, rPageSize
, aRelativePosition
, ePos
, bDefaultLegendSize
? rDefaultLegendSize
: aLegendSize
, bOverlay
);
1102 catch( const uno::Exception
& )
1104 DBG_UNHANDLED_EXCEPTION("chart2" );
1110 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */