xref: /aoo4110/main/chart2/source/view/main/VLegend.cxx (revision b1cdbd2c)
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