1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_chart2.hxx"
26 #include "VLegend.hxx"
27 #include "macros.hxx"
28 #include "PropertyMapper.hxx"
29 #include "CommonConverters.hxx"
30 #include "ObjectIdentifier.hxx"
31 #include "RelativePositionHelper.hxx"
32 #include "ShapeFactory.hxx"
33 #include "RelativeSizeHelper.hxx"
34 #include "LegendEntryProvider.hxx"
35 #include "chartview/DrawModelWrapper.hxx"
36 #include <com/sun/star/text/XTextRange.hpp>
37 #include <com/sun/star/text/WritingMode2.hpp>
38 #include <com/sun/star/beans/XPropertySet.hpp>
39 #include <com/sun/star/beans/XPropertyState.hpp>
40 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
41 #include <com/sun/star/drawing/LineJoint.hpp>
42 #include <com/sun/star/chart/ChartLegendExpansion.hpp>
43 #include <com/sun/star/chart2/LegendPosition.hpp>
44 #include <com/sun/star/chart2/RelativePosition.hpp>
45 #include <rtl/ustrbuf.hxx>
46 #include <svl/languageoptions.hxx>
47
48 #include <vector>
49 #include <algorithm>
50
51 using namespace ::com::sun::star;
52 using namespace ::com::sun::star::chart2;
53
54 using ::com::sun::star::uno::Reference;
55 using ::com::sun::star::uno::Sequence;
56 using ::rtl::OUString;
57 using ::rtl::OUStringBuffer;
58
59 //.............................................................................
60 namespace chart
61 {
62 //.............................................................................
63
64 namespace
65 {
66
67 typedef ::std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues;
68
69 typedef ::std::vector< ViewLegendEntry > tViewLegendEntryContainer;
70
lcl_CalcViewFontSize(const Reference<beans::XPropertySet> & xProp,const awt::Size & rReferenceSize)71 double lcl_CalcViewFontSize(
72 const Reference< beans::XPropertySet > & xProp,
73 const awt::Size & rReferenceSize )
74 {
75 double fResult = 10.0;
76
77 awt::Size aPropRefSize;
78 float fFontHeight( 0.0 );
79 if( xProp.is() && ( xProp->getPropertyValue( C2U( "CharHeight" )) >>= fFontHeight ))
80 {
81 fResult = fFontHeight;
82 try
83 {
84 if( (xProp->getPropertyValue( C2U( "ReferencePageSize" )) >>= aPropRefSize) &&
85 (aPropRefSize.Height > 0))
86 {
87 // todo: find out if asian text is really used
88 // Reference< beans::XPropertySetInfo >xInfo( xProp, uno::UNO_QUERY );
89 // float fFontHeight2 = fFontHeight;
90 // if( xInfo.is() &&
91 // xInfo->hasPropertyByName(C2U("CharHeightAsian")) &&
92 // (xProp->getPropertyValue(C2U("CharHeightAsian")) >>= fFontHeight2) &&
93 // fFontHeight2 > fFontHeight )
94 // {
95 // fFontHeight = fFontHeight2;
96 // }
97
98 // if( xInfo.is() &&
99 // xInfo->hasPropertyByName(C2U("CharHeightComplex")) &&
100 // (xProp->getPropertyValue(C2U("CharHeightComplex")) >>= fFontHeight2) &&
101 // fFontHeight2 > fFontHeight )
102 // {
103 // fFontHeight = fFontHeight2;
104 // }
105
106 fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize );
107 }
108 }
109 catch( const uno::Exception & ex )
110 {
111 ASSERT_EXCEPTION( ex );
112 }
113 }
114
115 // pt -> 1/100th mm
116 return (fResult * (2540.0 / 72.0));
117 }
118
lcl_getProperties(const Reference<beans::XPropertySet> & xLegendProp,tPropertyValues & rOutLineFillProperties,tPropertyValues & rOutTextProperties,const awt::Size & rReferenceSize)119 void lcl_getProperties(
120 const Reference< beans::XPropertySet > & xLegendProp,
121 tPropertyValues & rOutLineFillProperties,
122 tPropertyValues & rOutTextProperties,
123 const awt::Size & rReferenceSize )
124 {
125 // Get Line- and FillProperties from model legend
126 if( xLegendProp.is())
127 {
128 // set rOutLineFillProperties
129 ::chart::tPropertyNameValueMap aLineFillValueMap;
130 ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
131
132 aLineFillValueMap[ C2U("LineJoint") ] = uno::makeAny( drawing::LineJoint_ROUND );
133
134 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
135 rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
136
137 // set rOutTextProperties
138 ::chart::tPropertyNameValueMap aTextValueMap;
139 ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
140
141 drawing::TextHorizontalAdjust eHorizAdjust( drawing::TextHorizontalAdjust_LEFT );
142 aTextValueMap[ C2U("TextAutoGrowHeight") ] = uno::makeAny( sal_True );
143 aTextValueMap[ C2U("TextAutoGrowWidth") ] = uno::makeAny( sal_True );
144 aTextValueMap[ C2U("TextHorizontalAdjust") ] = uno::makeAny( eHorizAdjust );
145 aTextValueMap[ C2U("TextMaximumFrameWidth") ] = uno::makeAny( rReferenceSize.Width ); //needs to be overwritten by actual available space in the legend
146
147 // recalculate font size
148 awt::Size aPropRefSize;
149 float fFontHeight( 0.0 );
150 if( (xLegendProp->getPropertyValue( C2U( "ReferencePageSize" )) >>= aPropRefSize) &&
151 (aPropRefSize.Height > 0) &&
152 (aTextValueMap[ C2U("CharHeight") ] >>= fFontHeight) )
153 {
154 aTextValueMap[ C2U("CharHeight") ] = uno::makeAny(
155 static_cast< float >(
156 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
157
158 if( aTextValueMap[ C2U("CharHeightAsian") ] >>= fFontHeight )
159 {
160 aTextValueMap[ C2U("CharHeightAsian") ] = uno::makeAny(
161 static_cast< float >(
162 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
163 }
164 if( aTextValueMap[ C2U("CharHeightComplex") ] >>= fFontHeight )
165 {
166 aTextValueMap[ C2U("CharHeightComplex") ] = uno::makeAny(
167 static_cast< float >(
168 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
169 }
170 }
171
172 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
173 rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
174 }
175 }
176
lcl_createTextShapes(const tViewLegendEntryContainer & rEntries,const Reference<lang::XMultiServiceFactory> & xShapeFactory,const Reference<drawing::XShapes> & xTarget,::std::vector<Reference<drawing::XShape>> & rOutTextShapes,const tPropertyValues & rTextProperties)177 awt::Size lcl_createTextShapes(
178 const tViewLegendEntryContainer & rEntries,
179 const Reference< lang::XMultiServiceFactory > & xShapeFactory,
180 const Reference< drawing::XShapes > & xTarget,
181 ::std::vector< Reference< drawing::XShape > > & rOutTextShapes,
182 const tPropertyValues & rTextProperties )
183 {
184 awt::Size aResult;
185
186 for( tViewLegendEntryContainer::const_iterator aIt( rEntries.begin());
187 aIt != rEntries.end(); ++aIt )
188 {
189 try
190 {
191 // create label shape
192 Reference< drawing::XShape > xEntry(
193 xShapeFactory->createInstance(
194 C2U( "com.sun.star.drawing.TextShape" )), uno::UNO_QUERY_THROW );
195 xTarget->add( xEntry );
196
197 // set label text
198 Sequence< Reference< XFormattedString > > aLabelSeq = (*aIt).aLabel;
199 for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i )
200 {
201 // todo: support more than one text range
202 if( i == 1 )
203 break;
204
205 Reference< text::XTextRange > xRange( xEntry, uno::UNO_QUERY );
206 OUString aLabelString( aLabelSeq[i]->getString());
207 // workaround for Issue #i67540#
208 if( aLabelString.isEmpty() )
209 aLabelString = C2U(" ");
210 if( xRange.is())
211 xRange->setString( aLabelString );
212
213 PropertyMapper::setMultiProperties(
214 rTextProperties.first, rTextProperties.second,
215 Reference< beans::XPropertySet >( xRange, uno::UNO_QUERY ));
216
217 // adapt max-extent
218 awt::Size aCurrSize( xEntry->getSize());
219 aResult.Width = ::std::max( aResult.Width, aCurrSize.Width );
220 aResult.Height = ::std::max( aResult.Height, aCurrSize.Height );
221 }
222
223 rOutTextShapes.push_back( xEntry );
224 }
225 catch( uno::Exception & ex )
226 {
227 ASSERT_EXCEPTION( ex );
228 }
229 }
230
231 return aResult;
232 }
233
lcl_collectColumnWidths(std::vector<sal_Int32> & rColumnWidths,const sal_Int32 nNumberOfRows,const sal_Int32 nNumberOfColumns,const::std::vector<Reference<drawing::XShape>> aTextShapes,sal_Int32 nSymbolPlusDistanceWidth)234 void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns
235 , const ::std::vector< Reference< drawing::XShape > > aTextShapes, sal_Int32 nSymbolPlusDistanceWidth )
236 {
237 rColumnWidths.clear();
238 sal_Int32 nRow = 0;
239 sal_Int32 nColumn = 0;
240 sal_Int32 nNumberOfEntries = aTextShapes.size();
241 for( ; nRow < nNumberOfRows; ++nRow )
242 {
243 for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
244 {
245 sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
246 if( nEntry < nNumberOfEntries )
247 {
248 awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() );
249 sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
250 if( nRow==0 )
251 rColumnWidths.push_back( nWidth );
252 else
253 rColumnWidths[nColumn] = ::std::max( nWidth, rColumnWidths[nColumn] );
254 }
255 }
256 }
257 }
258
lcl_collectRowHeighs(std::vector<sal_Int32> & rRowHeights,const sal_Int32 nNumberOfRows,const sal_Int32 nNumberOfColumns,const::std::vector<Reference<drawing::XShape>> aTextShapes)259 void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns
260 , const ::std::vector< Reference< drawing::XShape > > aTextShapes )
261 {
262 // calculate maximum height for each row
263 // and collect column widths
264 rRowHeights.clear();
265 sal_Int32 nRow = 0;
266 sal_Int32 nColumn = 0;
267 sal_Int32 nNumberOfEntries = aTextShapes.size();
268 for( ; nRow < nNumberOfRows; ++nRow )
269 {
270 sal_Int32 nCurrentRowHeight = 0;
271 for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
272 {
273 sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
274 if( nEntry < nNumberOfEntries )
275 {
276 awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() );
277 nCurrentRowHeight = ::std::max( nCurrentRowHeight, aTextSize.Height );
278 }
279 }
280 rRowHeights.push_back( nCurrentRowHeight );
281 }
282 }
283
lcl_getTextLineHeight(const std::vector<sal_Int32> & aRowHeights,const sal_Int32 nNumberOfRows,double fViewFontSize)284 sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
285 {
286 const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
287 sal_Int32 nTextLineHeight = nFontHeight;
288 for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ )
289 {
290 sal_Int32 nFullTextHeight = aRowHeights[ nR ];
291 if( ( nFullTextHeight / nFontHeight ) <= 1 )
292 {
293 nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
294 break;
295 }
296 }
297 return nTextLineHeight;
298 }
299
300 //returns resulting legend size
lcl_placeLegendEntries(tViewLegendEntryContainer & rEntries,::com::sun::star::chart::ChartLegendExpansion eExpansion,bool bSymbolsLeftSide,double fViewFontSize,const awt::Size & rMaxSymbolExtent,tPropertyValues & rTextProperties,const Reference<drawing::XShapes> & xTarget,const Reference<lang::XMultiServiceFactory> & xShapeFactory,const awt::Size & rAvailableSpace)301 awt::Size lcl_placeLegendEntries(
302 tViewLegendEntryContainer & rEntries,
303 ::com::sun::star::chart::ChartLegendExpansion eExpansion,
304 bool bSymbolsLeftSide,
305 double fViewFontSize,
306 const awt::Size& rMaxSymbolExtent,
307 tPropertyValues & rTextProperties,
308 const Reference< drawing::XShapes > & xTarget,
309 const Reference< lang::XMultiServiceFactory > & xShapeFactory,
310 const awt::Size & rAvailableSpace )
311 {
312 bool bIsCustomSize = (eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM);
313 awt::Size aResultingLegendSize(0,0);
314 if( bIsCustomSize )
315 aResultingLegendSize = rAvailableSpace;
316
317 // #i109336# Improve auto positioning in chart
318 sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) );
319 //sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 200.0, fViewFontSize * 0.33 ) );
320 sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
321 sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
322 sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
323 //sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 230.0, fViewFontSize * 0.45 ) );
324
325 const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
326 const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
327 sal_Int32 nMaxTextWidth = rAvailableSpace.Width - (2 * nXPadding) - nSymbolPlusDistanceWidth;
328 rtl::OUString aPropNameTextMaximumFrameWidth( C2U("TextMaximumFrameWidth") );
329 uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, aPropNameTextMaximumFrameWidth);
330 if(pFrameWidthAny)
331 {
332 if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH )
333 {
334 // limit the width of texts to 30% of the total available width
335 // #i109336# Improve auto positioning in chart
336 nMaxTextWidth = rAvailableSpace.Width * 3 / 10;
337 }
338 *pFrameWidthAny = uno::makeAny(nMaxTextWidth);
339 }
340
341 ::std::vector< Reference< drawing::XShape > > aTextShapes;
342 awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties );
343 OSL_ASSERT( aTextShapes.size() == rEntries.size());
344
345 sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
346 sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
347 sal_Int32 nNumberOfEntries = rEntries.size();
348
349 sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
350 std::vector< sal_Int32 > aColumnWidths;
351 std::vector< sal_Int32 > aRowHeights;
352
353 sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
354
355 // determine layout depending on LegendExpansion
356 if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
357 {
358 sal_Int32 nCurrentRow=0;
359 sal_Int32 nCurrentColumn=-1;
360 sal_Int32 nColumnCount=0;
361 sal_Int32 nMaxColumnCount=-1;
362 for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ )
363 {
364 Reference< drawing::XShape > xShape( aTextShapes[nN] );
365 if( !xShape.is() )
366 continue;
367 awt::Size aSize( xShape->getSize() );
368 sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth;
369 sal_Int32 nCurrentColumnCount = aColumnWidths.size();
370
371 //are we allowed to add a new column?
372 if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount )
373 {
374 //try add a new column
375 nCurrentColumn++;
376 if( nCurrentColumn < nCurrentColumnCount )
377 {
378 //check wether the current column width is sufficient for the new entry
379 if( aColumnWidths[nCurrentColumn]>=nNewWidth )
380 {
381 //all good proceed with next entry
382 continue;
383 }
384 }
385 if( nCurrentColumn < nCurrentColumnCount )
386 aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] );
387 else
388 aColumnWidths.push_back(nNewWidth);
389
390 //do the columns still fit into the given size?
391 nCurrentColumnCount = aColumnWidths.size();//update count
392 sal_Int32 nSumWidth = 0;
393 for( sal_Int32 nC=0; nC<nCurrentColumnCount; nC++ )
394 nSumWidth += aColumnWidths[nC];
395
396 if( nSumWidth <= rAvailableSpace.Width || nCurrentColumnCount==1 )
397 {
398 //all good proceed with next entry
399 continue;
400 }
401 else
402 {
403 //not enough space for the current amount of columns
404 //try again with less columns
405 nMaxColumnCount = nCurrentColumnCount-1;
406 nN=-1;
407 nCurrentRow=0;
408 nCurrentColumn=-1;
409 nColumnCount=0;
410 aColumnWidths.clear();
411 }
412 }
413 else
414 {
415 //add a new row and try the same entry again
416 nCurrentRow++;
417 nCurrentColumn=-1;
418 nN--;
419 }
420 }
421 nNumberOfColumns = aColumnWidths.size();
422 nNumberOfRows = nCurrentRow+1;
423
424 //check if there is not enough space so that some entries must be removed
425 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
426 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
427 sal_Int32 nSumHeight = 0;
428 for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ )
429 nSumHeight += aRowHeights[nR];
430 sal_Int32 nRemainingSpace = rAvailableSpace.Height - nSumHeight;
431
432 if( nRemainingSpace<0 )
433 {
434 //remove entries that are too big
435 for( sal_Int32 nR=nNumberOfRows; nR--; )
436 {
437 for( sal_Int32 nC=nNumberOfColumns; nC--; )
438 {
439 sal_Int32 nEntry = (nC + nR * nNumberOfColumns);
440 if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) )
441 {
442 DrawModelWrapper::removeShape( aTextShapes[nEntry] );
443 aTextShapes.pop_back();
444 }
445 if( nEntry < nNumberOfEntries )
446 {
447 DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol );
448 rEntries.pop_back();
449 nNumberOfEntries--;
450 }
451 }
452 nSumHeight -= aRowHeights[nR];
453 aRowHeights.pop_back();
454 nRemainingSpace = rAvailableSpace.Height - nSumHeight;
455 if( nRemainingSpace>=0 )
456 break;
457 }
458 nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size());
459 }
460 if( nRemainingSpace > 0 )
461 {
462 sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset;
463 if( nRemainingSpace < nNormalSpacingHeight )
464 {
465 //reduce spacing between the entries
466 nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1);
467 }
468 else
469 {
470 //we have some space left that should be spread equally between all rows
471 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1);
472 nYPadding += nRemainingSingleSpace;
473 nYOffset += nRemainingSingleSpace;
474 }
475 }
476
477 //check spacing between columns
478 sal_Int32 nSumWidth = 0;
479 for( sal_Int32 nC=0; nC<nNumberOfColumns; nC++ )
480 nSumWidth += aColumnWidths[nC];
481 nRemainingSpace = rAvailableSpace.Width - nSumWidth;
482 if( nRemainingSpace>=0 )
483 {
484 sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset;
485 if( nRemainingSpace < nNormalSpacingWidth )
486 {
487 //reduce spacing between the entries
488 nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1);
489 }
490 else
491 {
492 //we have some space left that should be spread equally between all columns
493 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1);
494 nXPadding += nRemainingSingleSpace;
495 nXOffset += nRemainingSingleSpace;
496 }
497 }
498 }
499 else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH )
500 {
501 sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
502 ? (rAvailableSpace.Height - 2*nYPadding ) / nMaxEntryHeight
503 : 0;
504
505 nNumberOfColumns = nMaxNumberOfRows
506 ? static_cast< sal_Int32 >(
507 ceil( static_cast< double >( nNumberOfEntries ) /
508 static_cast< double >( nMaxNumberOfRows ) ))
509 : 0;
510 nNumberOfRows = nNumberOfColumns
511 ? static_cast< sal_Int32 >(
512 ceil( static_cast< double >( nNumberOfEntries ) /
513 static_cast< double >( nNumberOfColumns ) ))
514 : 0;
515 }
516 else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_WIDE )
517 {
518 sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth
519 ? (rAvailableSpace.Width - 2*nXPadding ) / nMaxEntryWidth
520 : 0;
521
522 nNumberOfRows = nMaxNumberOfColumns
523 ? static_cast< sal_Int32 >(
524 ceil( static_cast< double >( nNumberOfEntries ) /
525 static_cast< double >( nMaxNumberOfColumns ) ))
526 : 0;
527 nNumberOfColumns = nNumberOfRows
528 ? static_cast< sal_Int32 >(
529 ceil( static_cast< double >( nNumberOfEntries ) /
530 static_cast< double >( nNumberOfRows ) ))
531 : 0;
532 }
533 else // ::com::sun::star::chart::ChartLegendExpansion_BALANCED
534 {
535 double fAspect = nMaxEntryHeight
536 ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight )
537 : 0.0;
538
539 nNumberOfRows = static_cast< sal_Int32 >(
540 ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect )));
541 nNumberOfColumns = nNumberOfRows
542 ? static_cast< sal_Int32 >(
543 ceil( static_cast< double >( nNumberOfEntries ) /
544 static_cast< double >( nNumberOfRows ) ))
545 : 0;
546 }
547
548 if(nNumberOfRows<=0)
549 return aResultingLegendSize;
550
551 if( eExpansion != ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
552 {
553 lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
554 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
555 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
556 }
557
558 sal_Int32 nCurrentXPos = nXPadding;
559 sal_Int32 nCurrentYPos = nYPadding;
560 if( !bSymbolsLeftSide )
561 nCurrentXPos = -nXPadding;
562
563 // place entries into column and rows
564 sal_Int32 nMaxYPos = 0;
565 sal_Int32 nRow = 0;
566 sal_Int32 nColumn = 0;
567 for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
568 {
569 nCurrentYPos = nYPadding;
570 for( nRow = 0; nRow < nNumberOfRows; ++nRow )
571 {
572 sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
573 if( nEntry >= nNumberOfEntries )
574 break;
575
576 // text shape
577 Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] );
578 if( xTextShape.is() )
579 {
580 awt::Size aTextSize( xTextShape->getSize() );
581 sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
582 if( !bSymbolsLeftSide )
583 nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
584 xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
585 }
586
587 // symbol
588 Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
589 if( xSymbol.is() )
590 {
591 awt::Size aSymbolSize( rMaxSymbolExtent );
592 sal_Int32 nSymbolXPos = nCurrentXPos;
593 if( !bSymbolsLeftSide )
594 nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
595 sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
596 xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
597 }
598
599 nCurrentYPos += aRowHeights[ nRow ];
600 if( nRow+1 < nNumberOfRows )
601 nCurrentYPos += nYOffset;
602 nMaxYPos = ::std::max( nMaxYPos, nCurrentYPos );
603 }
604 if( bSymbolsLeftSide )
605 {
606 nCurrentXPos += aColumnWidths[nColumn];
607 if( nColumn+1 < nNumberOfColumns )
608 nCurrentXPos += nXOffset;
609 }
610 else
611 {
612 nCurrentXPos -= aColumnWidths[nColumn];
613 if( nColumn+1 < nNumberOfColumns )
614 nCurrentXPos -= nXOffset;
615 }
616 }
617
618 if( !bIsCustomSize )
619 {
620 if( bSymbolsLeftSide )
621 aResultingLegendSize.Width = nCurrentXPos + nXPadding;
622 else
623 {
624 sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
625 aResultingLegendSize.Width = nLegendWidth;
626 }
627 aResultingLegendSize.Height = nMaxYPos + nYPadding;
628 }
629
630 if( !bSymbolsLeftSide )
631 {
632 sal_Int32 nLegendWidth = aResultingLegendSize.Width;
633 awt::Point aPos(0,0);
634 for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ )
635 {
636 Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
637 aPos = xSymbol->getPosition();
638 aPos.X += nLegendWidth;
639 xSymbol->setPosition( aPos );
640 Reference< drawing::XShape > xText( aTextShapes[ nEntry ] );
641 aPos = xText->getPosition();
642 aPos.X += nLegendWidth;
643 xText->setPosition( aPos );
644 }
645 }
646
647 return aResultingLegendSize;
648 }
649
650 // #i109336# Improve auto positioning in chart
lcl_getLegendLeftRightMargin()651 sal_Int32 lcl_getLegendLeftRightMargin()
652 {
653 return 210; // 1/100 mm
654 }
655
656 // #i109336# Improve auto positioning in chart
lcl_getLegendTopBottomMargin()657 sal_Int32 lcl_getLegendTopBottomMargin()
658 {
659 return 185; // 1/100 mm
660 }
661
lcl_getDefaultPosition(LegendPosition ePos,const awt::Rectangle & rOutAvailableSpace,const awt::Size & rPageSize)662 chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
663 {
664 chart2::RelativePosition aResult;
665
666 switch( ePos )
667 {
668 case LegendPosition_LINE_START:
669 {
670 // #i109336# Improve auto positioning in chart
671 const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) /
672 static_cast< double >( rPageSize.Width ) );
673 aResult = chart2::RelativePosition(
674 fDefaultDistance, 0.5, drawing::Alignment_LEFT );
675 }
676 break;
677 case LegendPosition_LINE_END:
678 {
679 // #i109336# Improve auto positioning in chart
680 const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) /
681 static_cast< double >( rPageSize.Width ) );
682 aResult = chart2::RelativePosition(
683 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
684 }
685 break;
686 case LegendPosition_PAGE_START:
687 {
688 // #i109336# Improve auto positioning in chart
689 const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) /
690 static_cast< double >( rPageSize.Height ) );
691 double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance;
692 aResult = chart2::RelativePosition(
693 0.5, fDistance, drawing::Alignment_TOP );
694 }
695 break;
696 case LegendPosition_PAGE_END:
697 {
698 // #i109336# Improve auto positioning in chart
699 const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) /
700 static_cast< double >( rPageSize.Height ) );
701 aResult = chart2::RelativePosition(
702 0.5, 1.0 - fDefaultDistance, drawing::Alignment_BOTTOM );
703 }
704 break;
705
706 case LegendPosition_CUSTOM:
707 // to avoid warning
708 case LegendPosition_MAKE_FIXED_SIZE:
709 // nothing to be set
710 break;
711 }
712
713 return aResult;
714 }
715
716 /** @return
717 a point relative to the upper left corner that can be used for
718 XShape::setPosition()
719 */
lcl_calculatePositionAndRemainingSpace(awt::Rectangle & rRemainingSpace,const awt::Size & rPageSize,chart2::RelativePosition aRelPos,LegendPosition ePos,const awt::Size & aLegendSize)720 awt::Point lcl_calculatePositionAndRemainingSpace(
721 awt::Rectangle & rRemainingSpace,
722 const awt::Size & rPageSize,
723 chart2::RelativePosition aRelPos,
724 LegendPosition ePos,
725 const awt::Size& aLegendSize )
726 {
727 // calculate position
728 awt::Point aResult(
729 static_cast< sal_Int32 >( aRelPos.Primary * rPageSize.Width ),
730 static_cast< sal_Int32 >( aRelPos.Secondary * rPageSize.Height ));
731
732 aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
733 aResult, aLegendSize, aRelPos.Anchor );
734
735 // adapt rRemainingSpace if LegendPosition is not CUSTOM
736 // #i109336# Improve auto positioning in chart
737 sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
738 sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
739 switch( ePos )
740 {
741 case LegendPosition_LINE_START:
742 {
743 sal_Int32 nExtent = aLegendSize.Width;
744 rRemainingSpace.Width -= ( nExtent + nXDistance );
745 rRemainingSpace.X += ( nExtent + nXDistance );
746 }
747 break;
748 case LegendPosition_LINE_END:
749 {
750 rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
751 }
752 break;
753 case LegendPosition_PAGE_START:
754 {
755 sal_Int32 nExtent = aLegendSize.Height;
756 rRemainingSpace.Height -= ( nExtent + nYDistance );
757 rRemainingSpace.Y += ( nExtent + nYDistance );
758 }
759 break;
760 case LegendPosition_PAGE_END:
761 {
762 rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance );
763 }
764 break;
765
766 default:
767 // nothing
768 break;
769 }
770
771 // adjust the legend position. Esp. for old files that had slightly smaller legends
772 const sal_Int32 nEdgeDistance( 30 );
773 if( aResult.X + aLegendSize.Width > rPageSize.Width )
774 {
775 sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance );
776 if( nNewX > rPageSize.Width / 4 )
777 aResult.X = nNewX;
778 }
779 if( aResult.Y + aLegendSize.Height > rPageSize.Height )
780 {
781 sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance );
782 if( nNewY > rPageSize.Height / 4 )
783 aResult.Y = nNewY;
784 }
785
786 return aResult;
787 }
788
lcl_shouldSymbolsBePlacedOnTheLeftSide(const Reference<beans::XPropertySet> & xLegendProp,sal_Int16 nDefaultWritingMode)789 bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
790 {
791 bool bSymbolsLeftSide = true;
792 try
793 {
794 if( SvtLanguageOptions().IsCTLFontEnabled() )
795 {
796 if(xLegendProp.is())
797 {
798 sal_Int16 nWritingMode=-1;
799 if( (xLegendProp->getPropertyValue( C2U("WritingMode") ) >>= nWritingMode) )
800 {
801 if( nWritingMode == text::WritingMode2::PAGE )
802 nWritingMode = nDefaultWritingMode;
803 if( nWritingMode == text::WritingMode2::RL_TB )
804 bSymbolsLeftSide=false;
805 }
806 }
807 }
808 }
809 catch( uno::Exception & ex )
810 {
811 ASSERT_EXCEPTION( ex );
812 }
813 return bSymbolsLeftSide;
814 }
815
816 } // anonymous namespace
817
VLegend(const Reference<XLegend> & xLegend,const Reference<uno::XComponentContext> & xContext,const std::vector<LegendEntryProvider * > & rLegendEntryProviderList)818 VLegend::VLegend(
819 const Reference< XLegend > & xLegend,
820 const Reference< uno::XComponentContext > & xContext,
821 const std::vector< LegendEntryProvider* >& rLegendEntryProviderList ) :
822 m_xLegend( xLegend ),
823 m_xContext( xContext ),
824 m_aLegendEntryProviderList( rLegendEntryProviderList )
825 {
826 }
827
828 // ----------------------------------------
829
init(const Reference<drawing::XShapes> & xTargetPage,const Reference<lang::XMultiServiceFactory> & xFactory,const Reference<frame::XModel> & xModel)830 void VLegend::init(
831 const Reference< drawing::XShapes >& xTargetPage,
832 const Reference< lang::XMultiServiceFactory >& xFactory,
833 const Reference< frame::XModel >& xModel )
834 {
835 m_xTarget = xTargetPage;
836 m_xShapeFactory = xFactory;
837 m_xModel = xModel;
838 }
839
840 // ----------------------------------------
841
setDefaultWritingMode(sal_Int16 nDefaultWritingMode)842 void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
843 {
844 m_nDefaultWritingMode = nDefaultWritingMode;
845 }
846
847 // ----------------------------------------
848
isVisible(const Reference<XLegend> & xLegend)849 bool VLegend::isVisible( const Reference< XLegend > & xLegend )
850 {
851 if( ! xLegend.is())
852 return sal_False;
853
854 sal_Bool bShow = sal_False;
855 try
856 {
857 Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW );
858 xLegendProp->getPropertyValue( C2U( "Show" )) >>= bShow;
859 }
860 catch( uno::Exception & ex )
861 {
862 ASSERT_EXCEPTION( ex );
863 }
864
865 return bShow;
866 }
867
868 // ----------------------------------------
869
createShapes(const awt::Size & rAvailableSpace,const awt::Size & rPageSize)870 void VLegend::createShapes(
871 const awt::Size & rAvailableSpace,
872 const awt::Size & rPageSize )
873 {
874 if(! (m_xLegend.is() &&
875 m_xShapeFactory.is() &&
876 m_xTarget.is()))
877 return;
878
879 try
880 {
881 //create shape and add to page
882 m_xShape.set( m_xShapeFactory->createInstance(
883 C2U( "com.sun.star.drawing.GroupShape" )), uno::UNO_QUERY );
884 m_xTarget->add( m_xShape );
885
886 // set name to enable selection
887 {
888 OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( m_xLegend, m_xModel ) );
889 ShapeFactory::setShapeName( m_xShape, ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle ) );
890 }
891
892 // create and insert sub-shapes
893 Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY );
894 if( xLegendContainer.is())
895 {
896 Reference< drawing::XShape > xBorder(
897 m_xShapeFactory->createInstance(
898 C2U( "com.sun.star.drawing.RectangleShape" )), uno::UNO_QUERY );
899
900 // for quickly setting properties
901 tPropertyValues aLineFillProperties;
902 tPropertyValues aTextProperties;
903
904 Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY );
905 ::com::sun::star::chart::ChartLegendExpansion eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH;
906 awt::Size aLegendSize( rAvailableSpace );
907
908 if( xLegendProp.is())
909 {
910 // get Expansion property
911 xLegendProp->getPropertyValue( C2U( "Expansion" )) >>= eExpansion;
912 if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
913 {
914 RelativeSize aRelativeSize;
915 if ((xLegendProp->getPropertyValue( C2U( "RelativeSize" )) >>= aRelativeSize))
916 {
917 aLegendSize.Width = static_cast<sal_Int32>( ::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ) ); //i117185
918 aLegendSize.Height = static_cast<sal_Int32>( ::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ) ); //i117185
919 }
920 else
921 eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH;
922 }
923 lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize );
924 }
925
926 if( xBorder.is())
927 {
928 xLegendContainer->add( xBorder );
929
930 // apply legend properties
931 PropertyMapper::setMultiProperties(
932 aLineFillProperties.first, aLineFillProperties.second,
933 Reference< beans::XPropertySet >( xBorder, uno::UNO_QUERY ));
934
935 //because of this name this border will be used for marking the legend
936 ShapeFactory(m_xShapeFactory).setShapeName( xBorder, C2U("MarkHandles") );
937 }
938
939 // create entries
940 double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo
941 // #i109336# Improve auto positioning in chart
942 sal_Int32 nSymbolHeigth = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
943 sal_Int32 nSymbolWidth = static_cast< sal_Int32 >( nSymbolHeigth );
944
945 ::std::vector< LegendEntryProvider* >::const_iterator aIter = m_aLegendEntryProviderList.begin();
946 const ::std::vector< LegendEntryProvider* >::const_iterator aEnd = m_aLegendEntryProviderList.end();
947 for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; aIter++ )
948 {
949 LegendEntryProvider* pLegendEntryProvider( *aIter );
950 if( pLegendEntryProvider )
951 {
952 awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
953 sal_Int32 nCurrentWidth = aCurrentRatio.Width;
954 if( aCurrentRatio.Height > 0 )
955 {
956 nCurrentWidth = nSymbolHeigth* aCurrentRatio.Width/aCurrentRatio.Height;
957 }
958 nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
959 }
960 }
961 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeigth );
962
963 tViewLegendEntryContainer aViewEntries;
964 for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; aIter++ )
965 {
966 LegendEntryProvider* pLegendEntryProvider( *aIter );
967 if( pLegendEntryProvider )
968 {
969 std::vector< ViewLegendEntry > aNewEntries = pLegendEntryProvider->createLegendEntries( aMaxSymbolExtent, eExpansion, xLegendProp, xLegendContainer, m_xShapeFactory, m_xContext );
970 aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
971 }
972 }
973
974 bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode );
975
976 // place entries
977 aLegendSize = lcl_placeLegendEntries( aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, aMaxSymbolExtent
978 , aTextProperties, xLegendContainer, m_xShapeFactory, aLegendSize );
979
980 if( xBorder.is() )
981 xBorder->setSize( aLegendSize );
982 }
983 }
984 catch( uno::Exception & ex )
985 {
986 ASSERT_EXCEPTION( ex );
987 }
988 }
989
990 // ----------------------------------------
991
changePosition(awt::Rectangle & rOutAvailableSpace,const awt::Size & rPageSize)992 void VLegend::changePosition(
993 awt::Rectangle & rOutAvailableSpace,
994 const awt::Size & rPageSize )
995 {
996 if(! m_xShape.is())
997 return;
998
999 try
1000 {
1001 // determine position and alignment depending on default position
1002 awt::Size aLegendSize = m_xShape->getSize();
1003 Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW );
1004 chart2::RelativePosition aRelativePosition;
1005
1006 bool bAutoPosition =
1007 ! (xLegendProp->getPropertyValue( C2U( "RelativePosition" )) >>= aRelativePosition);
1008
1009 LegendPosition ePos = LegendPosition_CUSTOM;
1010 xLegendProp->getPropertyValue( C2U( "AnchorPosition" )) >>= ePos;
1011
1012 //calculate position
1013 if( bAutoPosition )
1014 {
1015 // auto position: relative to remaining space
1016 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1017 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1018 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1019 m_xShape->setPosition( aPos );
1020 }
1021 else
1022 {
1023 // manual position: relative to whole page
1024 awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height );
1025 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1026 aAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1027 m_xShape->setPosition( aPos );
1028
1029 if( ePos != LegendPosition_CUSTOM )
1030 {
1031 // calculate remaining space as if having autoposition:
1032 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1033 lcl_calculatePositionAndRemainingSpace(
1034 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
1035 }
1036 }
1037 }
1038 catch( uno::Exception & ex )
1039 {
1040 ASSERT_EXCEPTION( ex );
1041 }
1042 }
1043
1044 //.............................................................................
1045 } //namespace chart
1046 //.............................................................................
1047