xref: /core/chart2/source/view/charttypes/VSeriesPlotter.cxx (revision 85f88d71967e3ce46be4fa990e7213852eaadd38)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <cstddef>
21 #include <limits>
22 #include <memory>
23 #include <VSeriesPlotter.hxx>
24 #include <BaseGFXHelper.hxx>
25 #include <VLineProperties.hxx>
26 #include <ShapeFactory.hxx>
27 #include <Diagram.hxx>
28 #include <BaseCoordinateSystem.hxx>
29 #include <DataSeries.hxx>
30 #include <DataSeriesProperties.hxx>
31 
32 #include <CommonConverters.hxx>
33 #include <ExplicitCategoriesProvider.hxx>
34 #include <FormattedString.hxx>
35 #include <ObjectIdentifier.hxx>
36 #include <StatisticsHelper.hxx>
37 #include <PlottingPositionHelper.hxx>
38 #include <LabelPositionHelper.hxx>
39 #include <ChartType.hxx>
40 #include <Clipping.hxx>
41 #include <servicenames_charttypes.hxx>
42 #include <NumberFormatterWrapper.hxx>
43 #include <DataSeriesHelper.hxx>
44 #include <RegressionCurveModel.hxx>
45 #include <RegressionCurveHelper.hxx>
46 #include <VLegendSymbolFactory.hxx>
47 #include <FormattedStringHelper.hxx>
48 #include <RelativePositionHelper.hxx>
49 #include <DateHelper.hxx>
50 #include <DiagramHelper.hxx>
51 #include <defines.hxx>
52 #include <ChartModel.hxx>
53 
54 //only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
55 #include "BarChart.hxx"
56 #include "HistogramChart.hxx"
57 #include "PieChart.hxx"
58 #include "AreaChart.hxx"
59 #include "CandleStickChart.hxx"
60 #include "BubbleChart.hxx"
61 #include "NetChart.hxx"
62 #include <unonames.hxx>
63 #include <SpecialCharacters.hxx>
64 
65 #include <com/sun/star/chart2/DataPointLabel.hpp>
66 #include <com/sun/star/chart/ErrorBarStyle.hpp>
67 #include <com/sun/star/chart/TimeUnit.hpp>
68 #include <com/sun/star/chart2/MovingAverageType.hpp>
69 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
70 #include <com/sun/star/container/XChild.hpp>
71 #include <com/sun/star/chart2/RelativePosition.hpp>
72 #include <o3tl/safeint.hxx>
73 #include <tools/color.hxx>
74 #include <tools/UnitConversion.hxx>
75 #include <rtl/ustrbuf.hxx>
76 #include <rtl/math.hxx>
77 #include <basegfx/vector/b2dvector.hxx>
78 #include <com/sun/star/drawing/LineStyle.hpp>
79 #include <com/sun/star/util/XCloneable.hpp>
80 
81 #include <unotools/localedatawrapper.hxx>
82 #include <comphelper/sequence.hxx>
83 #include <utility>
84 #include <vcl/svapp.hxx>
85 #include <vcl/settings.hxx>
86 #include <comphelper/diagnose_ex.hxx>
87 #include <sal/log.hxx>
88 
89 #include <map>
90 
91 
92 namespace chart {
93 
94 using namespace ::com::sun::star;
95 using namespace ::com::sun::star::chart;
96 using namespace ::com::sun::star::chart2;
97 using namespace ::chart::DataSeriesProperties;
98 using ::com::sun::star::uno::Reference;
99 using ::com::sun::star::uno::Sequence;
100 
CachedYValues()101 VDataSeriesGroup::CachedYValues::CachedYValues()
102         : m_bValuesDirty(true)
103         , m_fMinimumY(0.0)
104         , m_fMaximumY(0.0)
105 {
106 }
107 
VDataSeriesGroup(std::unique_ptr<VDataSeries> pSeries)108 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
109         : m_aSeriesVector(1)
110         , m_bMaxPointCountDirty(true)
111         , m_nMaxPointCount(0)
112 {
113     m_aSeriesVector[0] = std::move(pSeries);
114 }
115 
VDataSeriesGroup(VDataSeriesGroup && other)116 VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
117         : m_aSeriesVector( std::move(other.m_aSeriesVector) )
118         , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
119         , m_nMaxPointCount( other.m_nMaxPointCount )
120         , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
121 {
122 }
123 
~VDataSeriesGroup()124 VDataSeriesGroup::~VDataSeriesGroup()
125 {
126 }
127 
deleteSeries()128 void VDataSeriesGroup::deleteSeries()
129 {
130     //delete all data series help objects:
131     m_aSeriesVector.clear();
132 }
133 
addSeries(std::unique_ptr<VDataSeries> pSeries)134 void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
135 {
136     m_aSeriesVector.push_back(std::move(pSeries));
137     m_bMaxPointCountDirty=true;
138 }
139 
getSeriesCount() const140 sal_Int32 VDataSeriesGroup::getSeriesCount() const
141 {
142     return m_aSeriesVector.size();
143 }
144 
VSeriesPlotter(rtl::Reference<ChartType> xChartTypeModel,sal_Int32 nDimensionCount,bool bCategoryXAxis)145 VSeriesPlotter::VSeriesPlotter( rtl::Reference<ChartType> xChartTypeModel
146                                , sal_Int32 nDimensionCount, bool bCategoryXAxis )
147         : PlotterBase( nDimensionCount )
148         , m_pMainPosHelper( nullptr )
149         , m_xChartTypeModel(std::move(xChartTypeModel))
150         , m_bCategoryXAxis(bCategoryXAxis)
151         , m_nTimeResolution(css::chart::TimeUnit::DAY)
152         , m_aNullDate(30,12,1899)
153         , m_pExplicitCategoriesProvider(nullptr)
154         , m_bPointsWereSkipped(false)
155         , m_bPieLabelsAllowToMove(false)
156         , m_aAvailableOuterRect(0, 0, 0, 0)
157 {
158     SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
159 }
160 
~VSeriesPlotter()161 VSeriesPlotter::~VSeriesPlotter()
162 {
163     //delete all data series help objects:
164     for (std::vector<VDataSeriesGroup>  & rGroupVector : m_aZSlots)
165     {
166         for (VDataSeriesGroup & rGroup : rGroupVector)
167         {
168             rGroup.deleteSeries();
169         }
170         rGroupVector.clear();
171     }
172     m_aZSlots.clear();
173 
174     m_aSecondaryPosHelperMap.clear();
175 
176     m_aSecondaryValueScales.clear();
177 }
178 
addSeries(std::unique_ptr<VDataSeries> pSeries,sal_Int32 zSlot,sal_Int32 xSlot,sal_Int32 ySlot)179 void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
180 {
181     //take ownership of pSeries
182 
183     OSL_PRECOND( pSeries, "series to add is NULL" );
184     if(!pSeries)
185         return;
186 
187     if(m_bCategoryXAxis)
188     {
189         if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
190             pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
191         else
192             pSeries->setCategoryXAxis();
193     }
194     else
195     {
196         if( m_pExplicitCategoriesProvider )
197             pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
198     }
199 
200     if(zSlot<0 || o3tl::make_unsigned(zSlot)>=m_aZSlots.size())
201     {
202         //new z slot
203         std::vector< VDataSeriesGroup > aZSlot;
204         aZSlot.emplace_back( std::move(pSeries) );
205         m_aZSlots.push_back( std::move(aZSlot) );
206     }
207     else
208     {
209         //existing zslot
210         std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
211 
212         if(xSlot<0 || o3tl::make_unsigned(xSlot)>=rXSlots.size())
213         {
214             //append the series to already existing x series
215             rXSlots.emplace_back( std::move(pSeries) );
216         }
217         else
218         {
219             //x slot is already occupied
220             //y slot decides what to do:
221 
222             VDataSeriesGroup& rYSlots = rXSlots[xSlot];
223             sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
224 
225             if( ySlot < -1 )
226             {
227                 //move all existing series in the xSlot to next slot
228                 //@todo
229                 OSL_FAIL( "Not implemented yet");
230             }
231             else if( ySlot == -1 || ySlot >= nYSlotCount)
232             {
233                 //append the series to already existing y series
234                 rYSlots.addSeries( std::move(pSeries) );
235             }
236             else
237             {
238                 //y slot is already occupied
239                 //insert at given y and x position
240 
241                 //@todo
242                 OSL_FAIL( "Not implemented yet");
243             }
244         }
245     }
246 }
247 
getPreferredDiagramAspectRatio() const248 drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
249 {
250     drawing::Direction3D aRet(1.0,1.0,1.0);
251     if (!m_pPosHelper)
252         return aRet;
253 
254     drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
255     aRet.DirectionZ = aScale.DirectionZ*0.2;
256     if(aRet.DirectionZ>1.0)
257         aRet.DirectionZ=1.0;
258     if(aRet.DirectionZ>10)
259         aRet.DirectionZ=10;
260     return aRet;
261 }
262 
releaseShapes()263 void VSeriesPlotter::releaseShapes()
264 {
265     for (std::vector<VDataSeriesGroup> const & rGroupVector :  m_aZSlots)
266     {
267         for (VDataSeriesGroup const & rGroup : rGroupVector)
268         {
269             //iterate through all series in this x slot
270             for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
271             {
272                 pSeries->releaseShapes();
273             }
274         }
275     }
276 }
277 
getSeriesGroupShape(VDataSeries * pDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)278 rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
279                                         , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
280 {
281     if( !pDataSeries->m_xGroupShape )
282         //create a group shape for this series and add to logic target:
283         pDataSeries->m_xGroupShape = createGroupShape( xTarget,pDataSeries->getCID() );
284     return pDataSeries->m_xGroupShape;
285 }
286 
getSeriesGroupShapeFrontChild(VDataSeries * pDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)287 rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
288                                         , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
289 {
290     if(!pDataSeries->m_xFrontSubGroupShape)
291     {
292         //ensure that the series group shape is already created
293         rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
294         //ensure that the back child is created first
295         getSeriesGroupShapeBackChild( pDataSeries, xTarget );
296         //use series group shape as parent for the new created front group shape
297         pDataSeries->m_xFrontSubGroupShape = createGroupShape( xSeriesShapes );
298     }
299     return pDataSeries->m_xFrontSubGroupShape;
300 }
301 
getSeriesGroupShapeBackChild(VDataSeries * pDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)302 rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
303                                         , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
304 {
305     if(!pDataSeries->m_xBackSubGroupShape)
306     {
307         //ensure that the series group shape is already created
308         rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
309         //use series group shape as parent for the new created back group shape
310         pDataSeries->m_xBackSubGroupShape = createGroupShape( xSeriesShapes );
311     }
312     return pDataSeries->m_xBackSubGroupShape;
313 }
314 
getLabelsGroupShape(VDataSeries & rDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTextTarget)315 rtl::Reference<SvxShapeGroup> VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
316                                         , const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget )
317 {
318     //xTextTarget needs to be a 2D shape container always!
319     if(!rDataSeries.m_xLabelsGroupShape)
320     {
321         //create a 2D group shape for texts of this series and add to text target:
322         rDataSeries.m_xLabelsGroupShape = ShapeFactory::createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
323     }
324     return rDataSeries.m_xLabelsGroupShape;
325 }
326 
getErrorBarsGroupShape(VDataSeries & rDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,bool bYError)327 rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
328                                         , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
329                                         , bool bYError )
330 {
331     rtl::Reference<SvxShapeGroupAnyD> &rShapeGroup =
332             bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
333 
334     if(!rShapeGroup)
335     {
336         //create a group shape for this series and add to logic target:
337         rShapeGroup = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
338     }
339     return rShapeGroup;
340 
341 }
342 
getLabelTextForValue(VDataSeries const & rDataSeries,sal_Int32 nPointIndex,double fValue,bool bAsPercentage)343 OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
344                 , sal_Int32 nPointIndex
345                 , double fValue
346                 , bool bAsPercentage )
347 {
348     OUString aNumber;
349 
350     if (m_apNumberFormatterWrapper)
351     {
352         sal_Int32 nNumberFormatKey = 0;
353         if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
354             nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
355         else if( bAsPercentage )
356         {
357             sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
358             if( nPercentFormat != -1 )
359                 nNumberFormatKey = nPercentFormat;
360         }
361         else
362         {
363             nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
364         }
365         if(nNumberFormatKey<0)
366             nNumberFormatKey=0;
367 
368         Color nLabelCol;
369         bool bColChanged;
370         aNumber = m_apNumberFormatterWrapper->getFormattedString(
371                 nNumberFormatKey, fValue, nLabelCol, bColChanged );
372         //@todo: change color of label if bColChanged is true
373     }
374     else
375     {
376         const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
377         const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
378         assert(aNumDecimalSep.getLength() > 0);
379         sal_Unicode cDecSeparator = aNumDecimalSep[0];
380         aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
381             , 3/*DecPlaces*/ , cDecSeparator );
382     }
383     return aNumber;
384 }
385 
createDataLabel(const rtl::Reference<SvxShapeGroupAnyD> & xTarget,VDataSeries & rDataSeries,sal_Int32 nPointIndex,double fValue,double fSumValue,const awt::Point & rScreenPosition2D,LabelAlignment eAlignment,sal_Int32 nOffset,sal_Int32 nTextWidth)386 rtl::Reference<SvxShapeText> VSeriesPlotter::createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
387                     , VDataSeries& rDataSeries
388                     , sal_Int32 nPointIndex
389                     , double fValue
390                     , double fSumValue
391                     , const awt::Point& rScreenPosition2D
392                     , LabelAlignment eAlignment
393                     , sal_Int32 nOffset
394                     , sal_Int32 nTextWidth )
395 {
396     rtl::Reference<SvxShapeText> xTextShape;
397     Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
398 
399     try
400     {
401         const uno::Reference< css::beans::XPropertySet > xPropertySet(
402             rDataSeries.getPropertiesOfPoint( nPointIndex ) );
403         if( xPropertySet.is() )
404         {
405             uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
406             if( aAny.hasValue() )
407             {
408                 aAny >>= aCustomLabels;
409             }
410         }
411 
412         awt::Point aScreenPosition2D(rScreenPosition2D);
413         if(eAlignment==LABEL_ALIGN_LEFT)
414             aScreenPosition2D.X -= nOffset;
415         else if(eAlignment==LABEL_ALIGN_RIGHT)
416             aScreenPosition2D.X += nOffset;
417         else if(eAlignment==LABEL_ALIGN_TOP)
418             aScreenPosition2D.Y -= nOffset;
419         else if(eAlignment==LABEL_ALIGN_BOTTOM)
420             aScreenPosition2D.Y += nOffset;
421 
422         rtl::Reference<SvxShapeGroup> xTarget_ =
423             ShapeFactory::createGroup2D(
424                 getLabelsGroupShape(rDataSeries, xTarget),
425                 ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
426 
427         //check whether the label needs to be created and how:
428         DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
429 
430         if( !pLabel )
431             return xTextShape;
432 
433         //prepare legend symbol
434 
435         // get the font size for the label through the "CharHeight" property
436         // attached to the passed data series entry.
437         // (By tracing font height values it results that for pie chart the
438         // font size is not the same for all labels, but here no font size
439         // modification occurs).
440         float fViewFontSize( 10.0 );
441         {
442             uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
443             if( xProps.is() )
444                 xProps->getPropertyValue( u"CharHeight"_ustr) >>= fViewFontSize;
445             fViewFontSize = convertPointToMm100(fViewFontSize);
446         }
447 
448         // the font height is used for computing the size of an optional legend
449         // symbol to be prepended to the text label.
450         rtl::Reference< SvxShapeGroup > xSymbol;
451         if(pLabel->ShowLegendSymbol)
452         {
453             sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6  );
454             awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
455             sal_Int32 nSymbolWidth = aCurrentRatio.Width;
456             if( aCurrentRatio.Height > 0 )
457             {
458                 nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
459             }
460             awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
461 
462             if( rDataSeries.isVaryColorsByPoint() )
463                 xSymbol = VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_ );
464             else
465                 xSymbol = VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_ );
466         }
467 
468         //prepare text
469         bool bTextWrap = false;
470         OUString aSeparator(u" "_ustr);
471         double fRotationDegrees = 0.0;
472         try
473         {
474             uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
475             if(xPointProps.is())
476             {
477                 xPointProps->getPropertyValue( u"TextWordWrap"_ustr ) >>= bTextWrap;
478                 xPointProps->getPropertyValue( u"LabelSeparator"_ustr ) >>= aSeparator;
479                 // Extract the optional text rotation through the
480                 // "TextRotation" property attached to the passed data point.
481                 xPointProps->getPropertyValue( u"TextRotation"_ustr ) >>= fRotationDegrees;
482             }
483         }
484         catch( const uno::Exception& )
485         {
486             TOOLS_WARN_EXCEPTION("chart2", "" );
487         }
488 
489         sal_Int32 nLineCountForSymbolsize = 0;
490         sal_uInt32 nTextListLength = 4;
491         sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
492         Sequence< OUString > aTextList( nTextListLength );
493 
494         bool bUseCustomLabel = nCustomLabelsCount > 0;
495         if( bUseCustomLabel )
496         {
497             nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
498             aSeparator = "";
499             aTextList = Sequence< OUString >( nTextListLength );
500             auto pTextList = aTextList.getArray();
501             for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
502             {
503                 switch( aCustomLabels[i]->getFieldType() )
504                 {
505                     case DataPointCustomLabelFieldType_VALUE:
506                     {
507                         pTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
508                         break;
509                     }
510                     case DataPointCustomLabelFieldType_CATEGORYNAME:
511                     {
512                         pTextList[i] = getCategoryName( nPointIndex );
513                         break;
514                     }
515                     case DataPointCustomLabelFieldType_SERIESNAME:
516                     {
517                         OUString aRole;
518                         if ( m_xChartTypeModel )
519                             aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
520                         const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
521                         pTextList[i] = xSeries->getLabelForRole( aRole );
522                         break;
523                     }
524                     case DataPointCustomLabelFieldType_PERCENTAGE:
525                     {
526                         if(fSumValue == 0.0)
527                            fSumValue = 1.0;
528                         fValue /= fSumValue;
529                         if(fValue < 0)
530                            fValue *= -1.0;
531 
532                         pTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
533                         break;
534                     }
535                     case DataPointCustomLabelFieldType_CELLRANGE:
536                     {
537                         if (aCustomLabels[i]->getDataLabelsRange())
538                             pTextList[i] = aCustomLabels[i]->getString();
539                         else
540                             pTextList[i] = OUString();
541                         break;
542                     }
543                     case DataPointCustomLabelFieldType_CELLREF:
544                     {
545                         // TODO: for now doesn't show placeholder
546                         pTextList[i] = OUString();
547                         break;
548                     }
549                     case DataPointCustomLabelFieldType_TEXT:
550                     {
551                         pTextList[i] = aCustomLabels[i]->getString();
552                         break;
553                     }
554                     case DataPointCustomLabelFieldType_NEWLINE:
555                     {
556                         pTextList[i] = "\n";
557                         break;
558                     }
559                     default:
560                     break;
561                 }
562                 aCustomLabels[i]->setString( aTextList[i] );
563             }
564         }
565         else
566         {
567             auto pTextList = aTextList.getArray();
568             if( pLabel->ShowCategoryName )
569             {
570                 pTextList[0] = getCategoryName( nPointIndex );
571             }
572 
573             if( pLabel->ShowSeriesName )
574             {
575                 OUString aRole;
576                 if ( m_xChartTypeModel )
577                     aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
578                 const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
579                 pTextList[1] = xSeries->getLabelForRole( aRole );
580             }
581 
582             if( pLabel->ShowNumber )
583             {
584                 pTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
585             }
586 
587             if( pLabel->ShowNumberInPercent )
588             {
589                 if(fSumValue==0.0)
590                     fSumValue=1.0;
591                 fValue /= fSumValue;
592                 if( fValue < 0 )
593                     fValue*=-1.0;
594 
595                 pTextList[3] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
596             }
597         }
598 
599         for (auto const& line : aTextList)
600         {
601             if( !line.isEmpty() )
602             {
603                 ++nLineCountForSymbolsize;
604             }
605         }
606 
607         //prepare properties for multipropertyset-interface of shape
608         tNameSequence* pPropNames;
609         tAnySequence* pPropValues;
610         if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
611             return xTextShape;
612 
613         // set text alignment for the text shape to be created.
614         LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
615 
616         // check if data series entry percent value and absolute value have to
617         // be appended to the text label, and what should be the separator
618         // character (comma, space, new line). In case it is a new line we get
619         // a multi-line label.
620         bool bMultiLineLabel = ( aSeparator == "\n" );
621 
622         if( bUseCustomLabel )
623         {
624             Sequence< uno::Reference< XFormattedString > > aFormattedLabels(
625                 comphelper::containerToSequence<uno::Reference<XFormattedString>>(aCustomLabels));
626 
627             // create text shape
628             xTextShape = ShapeFactory::
629                 createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
630                     ShapeFactory::makeTransformation( aScreenPosition2D ) );
631         }
632         else
633         {
634             // join text list elements
635             OUStringBuffer aText;
636             for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
637             {
638                 if( !aTextList[nN].isEmpty() )
639                 {
640                     if( !aText.isEmpty() )
641                     {
642                         aText.append(aSeparator);
643                     }
644                     aText.append( aTextList[nN] );
645                 }
646             }
647 
648             //create text shape
649             xTextShape = ShapeFactory::
650                 createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
651                     ShapeFactory::makeTransformation( aScreenPosition2D ) );
652         }
653 
654         if( !xTextShape.is() )
655             return xTextShape;
656 
657         // we need to use a default value for the maximum width property ?
658         if( nTextWidth == 0 && bTextWrap )
659         {
660             sal_Int32 nMinSize =
661                     (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
662                         ? m_aPageReferenceSize.Height
663                         : m_aPageReferenceSize.Width;
664             nTextWidth = nMinSize / 3;
665         }
666 
667         // in case text must be wrapped set the maximum width property
668         // for the text shape
669         if( nTextWidth != 0 && bTextWrap )
670         {
671             // compute the height of a line of text
672             if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
673             {
674                 nLineCountForSymbolsize = 1;
675             }
676             awt::Size aTextSize = xTextShape->getSize();
677             sal_Int32 aTextLineHeight =  aTextSize.Height / nLineCountForSymbolsize;
678 
679             // set maximum text width
680             uno::Any aTextMaximumFrameWidth( nTextWidth );
681             xTextShape->SvxShape::setPropertyValue( u"TextMaximumFrameWidth"_ustr, aTextMaximumFrameWidth );
682 
683             // compute the total lines of text
684             aTextSize = xTextShape->getSize();
685             nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
686         }
687 
688         // in case text is rotated, the transformation property of the text
689         // shape is modified.
690         if( fRotationDegrees != 0.0 )
691         {
692             const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
693             xTextShape->SvxShape::setPropertyValue( u"Transformation"_ustr, ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
694             LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
695         }
696 
697         awt::Point aTextShapePos(xTextShape->getPosition());
698         if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
699         {
700             awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
701             if( aRelPos.X != -1 )
702             {
703                 xTextShape->setPosition(aRelPos);
704                 if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
705                     // "ShowCustomLeaderLines"
706                     rDataSeries.getModel()->getFastPropertyValue( PROP_DATASERIES_SHOW_CUSTOM_LEADERLINES ).get<sal_Bool>())
707                 {
708                     const basegfx::B2IRectangle aRect(
709                         BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
710                     sal_Int32 nX1 = rScreenPosition2D.X;
711                     sal_Int32 nY1 = rScreenPosition2D.Y;
712                     const sal_Int32 nX2 = std::clamp(nX1, aRelPos.X, aRect.getMaxX());
713                     const sal_Int32 nY2 = std::clamp(nY1, aRect.getMinY(), aRect.getMaxY());
714 
715                     //when the line is very short compared to the page size don't create one
716                     ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
717                     double fPageDiagonaleLength
718                         = std::hypot(m_aPageReferenceSize.Width, m_aPageReferenceSize.Height);
719                     if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
720                     {
721                         drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
722 
723                         VLineProperties aVLineProperties;
724                         ShapeFactory::createLine2D(xTarget, aPoints, &aVLineProperties);
725                     }
726                 }
727             }
728         }
729 
730         // in case legend symbol has to be displayed, text shape position is
731         // slightly changed.
732         if( xSymbol.is() )
733         {
734             awt::Point aNewTextPos(xTextShape->getPosition());
735 
736             awt::Point aSymbolPosition(aNewTextPos);
737             awt::Size aSymbolSize( xSymbol->getSize() );
738             awt::Size aTextSize = xTextShape->getSize();
739 
740             sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
741             if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
742                 nLineCountForSymbolsize = 1;
743             aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
744 
745             if(eAlignment==LABEL_ALIGN_LEFT
746                 || eAlignment==LABEL_ALIGN_LEFT_TOP
747                 || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
748             {
749                 aSymbolPosition.X -= nXDiff;
750             }
751             else if(eAlignment==LABEL_ALIGN_RIGHT
752                 || eAlignment==LABEL_ALIGN_RIGHT_TOP
753                 || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
754             {
755                 aNewTextPos.X += nXDiff;
756             }
757             else if(eAlignment==LABEL_ALIGN_TOP
758                 || eAlignment==LABEL_ALIGN_BOTTOM
759                 || eAlignment==LABEL_ALIGN_CENTER )
760             {
761                 aSymbolPosition.X -= nXDiff/2;
762                 aNewTextPos.X += nXDiff/2;
763             }
764 
765             xSymbol->setPosition( aSymbolPosition );
766             xTextShape->setPosition( aNewTextPos );
767         }
768     }
769     catch( const uno::Exception& )
770     {
771         TOOLS_WARN_EXCEPTION("chart2", "" );
772     }
773 
774     return xTextShape;
775 }
776 
777 namespace
778 {
lcl_getErrorBarLogicLength(const uno::Sequence<double> & rData,const uno::Reference<beans::XPropertySet> & xProp,sal_Int32 nErrorBarStyle,sal_Int32 nIndex,bool bPositive,bool bYError)779 double lcl_getErrorBarLogicLength(
780     const uno::Sequence< double > & rData,
781     const uno::Reference< beans::XPropertySet >& xProp,
782     sal_Int32 nErrorBarStyle,
783     sal_Int32 nIndex,
784     bool bPositive,
785     bool bYError )
786 {
787     double fResult = std::numeric_limits<double>::quiet_NaN();
788     try
789     {
790         switch( nErrorBarStyle )
791         {
792             case css::chart::ErrorBarStyle::NONE:
793                 break;
794             case css::chart::ErrorBarStyle::VARIANCE:
795                 fResult = StatisticsHelper::getVariance( rData );
796                 break;
797             case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
798                 fResult = StatisticsHelper::getStandardDeviation( rData );
799                 break;
800             case css::chart::ErrorBarStyle::RELATIVE:
801             {
802                 double fPercent = 0;
803                 if( xProp->getPropertyValue( bPositive
804                                              ? u"PositiveError"_ustr
805                                              : u"NegativeError"_ustr ) >>= fPercent )
806                 {
807                     if( nIndex >=0 && nIndex < rData.getLength() &&
808                         ! std::isnan( rData[nIndex] ) &&
809                         ! std::isnan( fPercent ))
810                     {
811                         fResult = rData[nIndex] * fPercent / 100.0;
812                     }
813                 }
814             }
815             break;
816             case css::chart::ErrorBarStyle::ABSOLUTE:
817                 xProp->getPropertyValue( bPositive
818                                          ? u"PositiveError"_ustr
819                                          : u"NegativeError"_ustr ) >>= fResult;
820                 break;
821             case css::chart::ErrorBarStyle::ERROR_MARGIN:
822             {
823                 // todo: check if this is really what's called error-margin
824                 double fPercent = 0;
825                 if( xProp->getPropertyValue( bPositive
826                                              ? u"PositiveError"_ustr
827                                              : u"NegativeError"_ustr ) >>= fPercent )
828                 {
829                     double fMaxValue = -std::numeric_limits<double>::infinity();
830                     for(double d : rData)
831                     {
832                         if(fMaxValue < d)
833                             fMaxValue = d;
834                     }
835                     if( std::isfinite( fMaxValue ) &&
836                         std::isfinite( fPercent ))
837                     {
838                         fResult = fMaxValue * fPercent / 100.0;
839                     }
840                 }
841             }
842             break;
843             case css::chart::ErrorBarStyle::STANDARD_ERROR:
844                 fResult = StatisticsHelper::getStandardError( rData );
845                 break;
846             case css::chart::ErrorBarStyle::FROM_DATA:
847             {
848                 uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
849                 if( xErrorBarData.is())
850                     fResult = StatisticsHelper::getErrorFromDataSource(
851                         xErrorBarData, nIndex, bPositive, bYError);
852             }
853             break;
854         }
855     }
856     catch( const uno::Exception & )
857     {
858         TOOLS_WARN_EXCEPTION("chart2", "" );
859     }
860 
861     return fResult;
862 }
863 
lcl_AddErrorBottomLine(const drawing::Position3D & rPosition,::basegfx::B2DVector aMainDirection,std::vector<std::vector<css::drawing::Position3D>> & rPoly,sal_Int32 nSequenceIndex)864 void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
865                 , std::vector<std::vector<css::drawing::Position3D>>& rPoly, sal_Int32 nSequenceIndex )
866 {
867     double fFixedWidth = 200.0;
868 
869     aMainDirection.normalize();
870     ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
871     aOrthoDirection.normalize();
872 
873     ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
874     ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
875     ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
876 
877     AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
878     AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
879 }
880 
lcl_getErrorBarMainDirection(const drawing::Position3D & rStart,const drawing::Position3D & rBottomEnd,PlottingPositionHelper const * pPosHelper,const drawing::Position3D & rUnscaledLogicPosition,bool bYError)881 ::basegfx::B2DVector lcl_getErrorBarMainDirection(
882               const drawing::Position3D& rStart
883             , const drawing::Position3D& rBottomEnd
884             , PlottingPositionHelper const * pPosHelper
885             , const drawing::Position3D& rUnscaledLogicPosition
886             , bool bYError )
887 {
888     ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
889                                          , rStart.PositionY - rBottomEnd.PositionY );
890     if( !aMainDirection.getLength() )
891     {
892         //get logic clip values:
893         double MinX = pPosHelper->getLogicMinX();
894         double MinY = pPosHelper->getLogicMinY();
895         double MaxX = pPosHelper->getLogicMaxX();
896         double MaxY = pPosHelper->getLogicMaxY();
897         double fZ = pPosHelper->getLogicMinZ();
898 
899         if( bYError )
900         {
901             //main direction has constant x value
902             MinX = rUnscaledLogicPosition.PositionX;
903             MaxX = rUnscaledLogicPosition.PositionX;
904         }
905         else
906         {
907             //main direction has constant y value
908             MinY = rUnscaledLogicPosition.PositionY;
909             MaxY = rUnscaledLogicPosition.PositionY;
910         }
911 
912         drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
913         drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
914 
915         aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
916                                               , aStart.PositionY - aEnd.PositionY );
917     }
918     if( !aMainDirection.getLength() )
919     {
920         //@todo
921     }
922     return aMainDirection;
923 }
924 
lcl_transformMixedToScene(PlottingPositionHelper const * pPosHelper,double fX,double fY,double fZ)925 drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
926     , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
927 {
928     if(!pPosHelper)
929         return drawing::Position3D(0,0,0);
930     pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
931     pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
932     return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
933 }
934 
935 } // anonymous namespace
936 
createErrorBar(const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const drawing::Position3D & rUnscaledLogicPosition,const uno::Reference<beans::XPropertySet> & xErrorBarProperties,const VDataSeries & rVDataSeries,sal_Int32 nIndex,bool bYError,const double * pfScaledLogicX)937 void VSeriesPlotter::createErrorBar(
938       const rtl::Reference<SvxShapeGroupAnyD>& xTarget
939     , const drawing::Position3D& rUnscaledLogicPosition
940     , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
941     , const VDataSeries& rVDataSeries
942     , sal_Int32 nIndex
943     , bool bYError /* = true */
944     , const double* pfScaledLogicX
945     )
946 {
947     if (!m_xChartTypeModel->isSupportingStatisticProperties(m_nDimension))
948         return;
949 
950     if( ! xErrorBarProperties.is())
951         return;
952 
953     try
954     {
955         bool bShowPositive = false;
956         bool bShowNegative = false;
957         sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
958 
959         xErrorBarProperties->getPropertyValue( u"ShowPositiveError"_ustr) >>= bShowPositive;
960         xErrorBarProperties->getPropertyValue( u"ShowNegativeError"_ustr) >>= bShowNegative;
961         xErrorBarProperties->getPropertyValue( u"ErrorBarStyle"_ustr) >>= nErrorBarStyle;
962 
963         if(!bShowPositive && !bShowNegative)
964             return;
965 
966         if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
967             return;
968 
969         if (!m_pPosHelper)
970             return;
971 
972         drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
973         if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
974         {
975             if (bYError)
976                 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
977             else
978                 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
979         }
980 
981         bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
982         bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
983         drawing::Position3D aMiddle(aUnscaledLogicPosition);
984         const double fX = aUnscaledLogicPosition.PositionX;
985         const double fY = aUnscaledLogicPosition.PositionY;
986         const double fZ = aUnscaledLogicPosition.PositionZ;
987         double fScaledX = fX;
988         if( pfScaledLogicX )
989             fScaledX = *pfScaledLogicX;
990         else
991             m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
992 
993         aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
994 
995         drawing::Position3D aNegative(aMiddle);
996         drawing::Position3D aPositive(aMiddle);
997 
998         uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
999 
1000         if( bShowPositive )
1001         {
1002             double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
1003             if( std::isfinite( fLength ) )
1004             {
1005                 double fLocalX = fX;
1006                 double fLocalY = fY;
1007                 if( bYError )
1008                 {
1009                     fLocalY+=fLength;
1010                     aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1011                 }
1012                 else
1013                 {
1014                     fLocalX+=fLength;
1015                     aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1016                 }
1017                 bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
1018             }
1019             else
1020                 bShowPositive = false;
1021         }
1022 
1023         if( bShowNegative )
1024         {
1025             double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
1026             if( std::isfinite( fLength ) )
1027             {
1028                 double fLocalX = fX;
1029                 double fLocalY = fY;
1030                 if( bYError )
1031                 {
1032                     fLocalY-=fLength;
1033                     aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1034                 }
1035                 else
1036                 {
1037                     fLocalX-=fLength;
1038                     aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1039                 }
1040                 if (std::isfinite(aNegative.PositionX) &&
1041                         std::isfinite(aNegative.PositionY) &&
1042                         std::isfinite(aNegative.PositionZ)) {
1043                     bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
1044                 } else {
1045                     // If error bars result in a numerical problem (e.g., an
1046                     // error bar on a logarithmic chart that results in a point
1047                     // <= 0) then just turn off the error bar.
1048                     //
1049                     // TODO: This perhaps should display a warning, so the user
1050                     // knows why a bar is not appearing.
1051                     // TODO: This test could also be added to the positive case,
1052                     // though a numerical overflow there is less likely.
1053                     bShowNegative = false;
1054                 }
1055             }
1056             else
1057                 bShowNegative = false;
1058         }
1059 
1060         if(!bShowPositive && !bShowNegative)
1061             return;
1062 
1063         std::vector<std::vector<css::drawing::Position3D>> aPoly;
1064 
1065         sal_Int32 nSequenceIndex=0;
1066         if( bShowNegative )
1067             AddPointToPoly( aPoly, aNegative, nSequenceIndex );
1068         AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
1069         if( bShowPositive )
1070             AddPointToPoly( aPoly, aPositive, nSequenceIndex );
1071 
1072         if( bShowNegative && bCreateNegativeBorder )
1073         {
1074             ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
1075             nSequenceIndex++;
1076             lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
1077         }
1078         if( bShowPositive && bCreatePositiveBorder )
1079         {
1080             ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
1081             nSequenceIndex++;
1082             lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
1083         }
1084 
1085         rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( xTarget, aPoly );
1086         PropertyMapper::setMappedProperties( *xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
1087     }
1088     catch( const uno::Exception & )
1089     {
1090         TOOLS_WARN_EXCEPTION("chart2", "" );
1091     }
1092 
1093 }
1094 
addErrorBorder(const drawing::Position3D & rPos0,const drawing::Position3D & rPos1,const rtl::Reference<SvxShapeGroupAnyD> & rTarget,const uno::Reference<beans::XPropertySet> & rErrorBorderProp)1095 void VSeriesPlotter::addErrorBorder(
1096       const drawing::Position3D& rPos0
1097      ,const drawing::Position3D& rPos1
1098      ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
1099      ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
1100 {
1101     std::vector<std::vector<css::drawing::Position3D>> aPoly { { rPos0, rPos1} };
1102     rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
1103                     rTarget, aPoly );
1104     PropertyMapper::setMappedProperties( *xShape, rErrorBorderProp,
1105                     PropertyMapper::getPropertyNameMapForLineProperties() );
1106 }
1107 
createErrorRectangle(const drawing::Position3D & rUnscaledLogicPosition,VDataSeries & rVDataSeries,sal_Int32 nIndex,const rtl::Reference<SvxShapeGroupAnyD> & rTarget,bool bUseXErrorData,bool bUseYErrorData)1108 void VSeriesPlotter::createErrorRectangle(
1109       const drawing::Position3D& rUnscaledLogicPosition
1110      ,VDataSeries& rVDataSeries
1111      ,sal_Int32 nIndex
1112      ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
1113      ,bool bUseXErrorData
1114      ,bool bUseYErrorData )
1115 {
1116     if ( m_nDimension != 2 )
1117         return;
1118 
1119     // error border properties
1120     Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
1121     if ( bUseXErrorData )
1122     {
1123         xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
1124         if ( !xErrorBorderPropX.is() )
1125             return;
1126     }
1127     rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesX =
1128         getErrorBarsGroupShape( rVDataSeries, rTarget, false );
1129 
1130     if ( bUseYErrorData )
1131     {
1132         xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
1133         if ( !xErrorBorderPropY.is() )
1134             return;
1135     }
1136     rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesY =
1137         getErrorBarsGroupShape( rVDataSeries, rTarget, true );
1138 
1139     if (!m_xChartTypeModel->isSupportingStatisticProperties(m_nDimension))
1140         return;
1141 
1142     try
1143     {
1144         bool bShowXPositive = false;
1145         bool bShowXNegative = false;
1146         bool bShowYPositive = false;
1147         bool bShowYNegative = false;
1148 
1149         sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
1150         sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
1151 
1152         if ( bUseXErrorData )
1153         {
1154             xErrorBorderPropX->getPropertyValue( u"ErrorBarStyle"_ustr ) >>= nErrorBorderStyleX;
1155             xErrorBorderPropX->getPropertyValue( u"ShowPositiveError"_ustr) >>= bShowXPositive;
1156             xErrorBorderPropX->getPropertyValue( u"ShowNegativeError"_ustr) >>= bShowXNegative;
1157         }
1158         if ( bUseYErrorData )
1159         {
1160             xErrorBorderPropY->getPropertyValue( u"ErrorBarStyle"_ustr ) >>= nErrorBorderStyleY;
1161             xErrorBorderPropY->getPropertyValue( u"ShowPositiveError"_ustr) >>= bShowYPositive;
1162             xErrorBorderPropY->getPropertyValue( u"ShowNegativeError"_ustr) >>= bShowYNegative;
1163         }
1164 
1165         if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
1166             bUseXErrorData = false;
1167         if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
1168             bUseYErrorData = false;
1169 
1170         if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
1171             return;
1172 
1173         if ( !m_pPosHelper )
1174             return;
1175 
1176         drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
1177         if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1178             aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
1179         if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1180             aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
1181 
1182         const double fX = aUnscaledLogicPosition.PositionX;
1183         const double fY = aUnscaledLogicPosition.PositionY;
1184         const double fZ = aUnscaledLogicPosition.PositionZ;
1185         double fScaledX = fX;
1186         m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1187 
1188         const uno::Sequence< double >& aDataX( rVDataSeries.getAllX() );
1189         const uno::Sequence< double >& aDataY( rVDataSeries.getAllY() );
1190 
1191         double fPosX = 0.0;
1192         double fPosY = 0.0;
1193         double fNegX = 0.0;
1194         double fNegY = 0.0;
1195         if ( bUseXErrorData )
1196         {
1197             if ( bShowXPositive )
1198                 fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1199                                 nErrorBorderStyleX, nIndex, true, false );
1200             if ( bShowXNegative )
1201                 fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1202                                 nErrorBorderStyleX, nIndex, false, false );
1203         }
1204 
1205         if ( bUseYErrorData )
1206         {
1207             if ( bShowYPositive )
1208                 fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1209                                 nErrorBorderStyleY, nIndex, true, true );
1210             if ( bShowYNegative )
1211                 fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1212                                 nErrorBorderStyleY, nIndex, false, true );
1213         }
1214 
1215         if ( !( std::isfinite( fPosX ) &&
1216                 std::isfinite( fPosY ) &&
1217                 std::isfinite( fNegX ) &&
1218                 std::isfinite( fNegY ) ) )
1219             return;
1220 
1221         drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
1222                                              fX - fNegX, fY - fNegY, fZ ) );
1223         drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
1224                                              fX - fNegX, fY + fPosY, fZ ) );
1225         drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
1226                                              fX + fPosX, fY + fPosY, fZ ) );
1227         drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
1228                                              fX + fPosX, fY - fNegY, fZ ) );
1229         if ( bUseXErrorData )
1230         {
1231             // top border
1232             addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
1233 
1234             // bottom border
1235             addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
1236         }
1237 
1238         if ( bUseYErrorData )
1239         {
1240             // left border
1241             addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
1242 
1243             // right border
1244             addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
1245         }
1246     }
1247     catch( const uno::Exception & )
1248     {
1249         DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
1250     }
1251 }
1252 
createErrorBar_X(const drawing::Position3D & rUnscaledLogicPosition,VDataSeries & rVDataSeries,sal_Int32 nPointIndex,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)1253 void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
1254                             , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1255                             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
1256 {
1257     if(m_nDimension!=2)
1258         return;
1259     // error bars
1260     uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
1261     if( xErrorBarProp.is())
1262     {
1263         rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
1264             getErrorBarsGroupShape(rVDataSeries, xTarget, false);
1265 
1266         createErrorBar( xErrorBarsGroup_Shapes
1267             , rUnscaledLogicPosition, xErrorBarProp
1268             , rVDataSeries, nPointIndex
1269             , false /* bYError */
1270             , nullptr );
1271     }
1272 }
1273 
createErrorBar_Y(const drawing::Position3D & rUnscaledLogicPosition,VDataSeries & rVDataSeries,sal_Int32 nPointIndex,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,double const * pfScaledLogicX)1274 void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
1275                             , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1276                             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
1277                             , double const * pfScaledLogicX )
1278 {
1279     if(m_nDimension!=2)
1280         return;
1281     // error bars
1282     uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
1283     if( xErrorBarProp.is())
1284     {
1285         rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
1286             getErrorBarsGroupShape(rVDataSeries, xTarget, true);
1287 
1288         createErrorBar( xErrorBarsGroup_Shapes
1289             , rUnscaledLogicPosition, xErrorBarProp
1290             , rVDataSeries, nPointIndex
1291             , true /* bYError */
1292             , pfScaledLogicX );
1293     }
1294 }
1295 
createRegressionCurvesShapes(VDataSeries const & rVDataSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const rtl::Reference<SvxShapeGroupAnyD> & xEquationTarget,bool bMaySkipPoints)1296 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
1297                             const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
1298                             const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
1299                             bool bMaySkipPoints )
1300 {
1301     if(m_nDimension!=2)
1302         return;
1303     const rtl::Reference< DataSeries >& xContainer( rVDataSeries.getModel() );
1304     if(!xContainer.is())
1305         return;
1306 
1307     if (!m_pPosHelper)
1308         return;
1309 
1310     const std::vector< rtl::Reference< ::chart::RegressionCurveModel > > & aCurveList = xContainer->getRegressionCurves2();
1311 
1312     for(std::size_t nN=0; nN<aCurveList.size(); nN++)
1313     {
1314         const auto & rCurve = aCurveList[nN];
1315         uno::Reference< XRegressionCurveCalculator > xCalculator( rCurve->getCalculator() );
1316         if( !xCalculator.is())
1317             continue;
1318 
1319         bool bAverageLine = RegressionCurveHelper::isMeanValueLine( rCurve );
1320 
1321         sal_Int32 aDegree = 2;
1322         sal_Int32 aPeriod = 2;
1323         sal_Int32 aMovingAverageType = css::chart2::MovingAverageType::Prior;
1324         double aExtrapolateForward = 0.0;
1325         double aExtrapolateBackward = 0.0;
1326         bool bForceIntercept = false;
1327         double aInterceptValue = 0.0;
1328 
1329         if ( !bAverageLine )
1330         {
1331             rCurve->getPropertyValue( u"PolynomialDegree"_ustr) >>= aDegree;
1332             rCurve->getPropertyValue( u"MovingAveragePeriod"_ustr) >>= aPeriod;
1333             rCurve->getPropertyValue( u"MovingAverageType"_ustr) >>= aMovingAverageType;
1334             rCurve->getPropertyValue( u"ExtrapolateForward"_ustr) >>= aExtrapolateForward;
1335             rCurve->getPropertyValue( u"ExtrapolateBackward"_ustr) >>= aExtrapolateBackward;
1336             rCurve->getPropertyValue( u"ForceIntercept"_ustr) >>= bForceIntercept;
1337             if (bForceIntercept)
1338                 rCurve->getPropertyValue( u"InterceptValue"_ustr) >>= aInterceptValue;
1339         }
1340 
1341         double fChartMinX = m_pPosHelper->getLogicMinX();
1342         double fChartMaxX = m_pPosHelper->getLogicMaxX();
1343 
1344         double fMinX = fChartMinX;
1345         double fMaxX = fChartMaxX;
1346 
1347         double fPointScale = 1.0;
1348 
1349         if( !bAverageLine )
1350         {
1351             rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
1352             fMaxX += aExtrapolateForward;
1353             fMinX -= aExtrapolateBackward;
1354 
1355             fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
1356             // sanitize the value, tdf#119922
1357             fPointScale = std::min(fPointScale, 1000.0);
1358         }
1359 
1360         xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod,
1361                                              aMovingAverageType);
1362         xCalculator->recalculateRegression(rVDataSeries.getAllX(), rVDataSeries.getAllY());
1363         sal_Int32 nPointCount = 100 * fPointScale;
1364 
1365         if ( nPointCount < 2 )
1366             nPointCount = 2;
1367 
1368         std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
1369         uno::Reference< chart2::XScaling > xScalingX;
1370         uno::Reference< chart2::XScaling > xScalingY;
1371         if( aScales.size() >= 2 )
1372         {
1373             xScalingX.set( aScales[0].Scaling );
1374             xScalingY.set( aScales[1].Scaling );
1375         }
1376 
1377         const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
1378             xCalculator->getCurveValues(
1379                             fMinX, fMaxX, nPointCount,
1380                             xScalingX, xScalingY, bMaySkipPoints ));
1381 
1382         nPointCount = aCalculatedPoints.getLength();
1383 
1384         drawing::PolyPolygonShape3D aRegressionPoly;
1385         aRegressionPoly.SequenceX.realloc(1);
1386         aRegressionPoly.SequenceY.realloc(1);
1387         aRegressionPoly.SequenceZ.realloc(1);
1388         auto pSequenceX = aRegressionPoly.SequenceX.getArray();
1389         auto pSequenceY = aRegressionPoly.SequenceY.getArray();
1390         auto pSequenceZ = aRegressionPoly.SequenceZ.getArray();
1391         pSequenceX[0].realloc(nPointCount);
1392         pSequenceY[0].realloc(nPointCount);
1393         auto pSequenceX0 = pSequenceX[0].getArray();
1394         auto pSequenceY0 = pSequenceY[0].getArray();
1395 
1396         sal_Int32 nRealPointCount = 0;
1397 
1398         for(geometry::RealPoint2D const & p : aCalculatedPoints)
1399         {
1400             double fLogicX = p.X;
1401             double fLogicY = p.Y;
1402             double fLogicZ = 0.0; //dummy
1403 
1404             // fdo#51656: don't scale mean value lines
1405             if(!bAverageLine)
1406                 m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
1407 
1408             if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
1409                !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
1410                !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
1411             {
1412                 pSequenceX0[nRealPointCount] = fLogicX;
1413                 pSequenceY0[nRealPointCount] = fLogicY;
1414                 nRealPointCount++;
1415             }
1416         }
1417         pSequenceX[0].realloc(nRealPointCount);
1418         pSequenceY[0].realloc(nRealPointCount);
1419         pSequenceZ[0].realloc(nRealPointCount);
1420 
1421         drawing::PolyPolygonShape3D aClippedPoly;
1422         Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
1423         aRegressionPoly = std::move(aClippedPoly);
1424         m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
1425 
1426         awt::Point aDefaultPos;
1427         if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
1428         {
1429             VLineProperties aVLineProperties;
1430             aVLineProperties.initFromPropertySet( rCurve );
1431 
1432             //create an extra group shape for each curve for selection handling
1433             rtl::Reference<SvxShapeGroupAnyD> xRegressionGroupShapes =
1434                 createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
1435             rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
1436                 xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
1437             ShapeFactory::setShapeName( xShape, u"MarkHandles"_ustr );
1438             aDefaultPos = xShape->getPosition();
1439         }
1440 
1441         // curve equation and correlation coefficient
1442         uno::Reference< beans::XPropertySet > xEquationProperties( rCurve->getEquationProperties());
1443         if( xEquationProperties.is())
1444         {
1445             createRegressionCurveEquationShapes(
1446                 rVDataSeries.getDataCurveEquationCID( nN ),
1447                 xEquationProperties, xEquationTarget, xCalculator,
1448                 aDefaultPos );
1449         }
1450     }
1451 }
1452 
lcl_getOUStringMaxLineLength(OUStringBuffer const & aString)1453 static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
1454 {
1455     const sal_Int32 nStringLength = aString.getLength();
1456     sal_Int32 nMaxLineLength = 0;
1457 
1458     for ( sal_Int32 i=0; i<nStringLength; i++ )
1459     {
1460         sal_Int32 indexSep = aString.indexOf( "\n", i );
1461         if ( indexSep < 0 )
1462             indexSep = nStringLength;
1463         sal_Int32 nLineLength = indexSep - i;
1464         if ( nLineLength > nMaxLineLength )
1465             nMaxLineLength = nLineLength;
1466         i = indexSep;
1467     }
1468 
1469     return nMaxLineLength;
1470 }
1471 
createRegressionCurveEquationShapes(const OUString & rEquationCID,const uno::Reference<beans::XPropertySet> & xEquationProperties,const rtl::Reference<SvxShapeGroupAnyD> & xEquationTarget,const uno::Reference<chart2::XRegressionCurveCalculator> & xRegressionCurveCalculator,awt::Point aDefaultPos)1472 void VSeriesPlotter::createRegressionCurveEquationShapes(
1473     const OUString & rEquationCID,
1474     const uno::Reference< beans::XPropertySet > & xEquationProperties,
1475     const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
1476     const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
1477     awt::Point aDefaultPos )
1478 {
1479     OSL_ASSERT( xEquationProperties.is());
1480     if( !xEquationProperties.is())
1481         return;
1482 
1483     bool bShowEquation = false;
1484     bool bShowCorrCoeff = false;
1485     if(!(( xEquationProperties->getPropertyValue( u"ShowEquation"_ustr) >>= bShowEquation ) &&
1486        ( xEquationProperties->getPropertyValue( u"ShowCorrelationCoefficient"_ustr) >>= bShowCorrCoeff )))
1487         return;
1488 
1489     if( ! (bShowEquation || bShowCorrCoeff))
1490         return;
1491 
1492     OUStringBuffer aFormula;
1493     sal_Int32 nNumberFormatKey = 0;
1494     sal_Int32 nFormulaWidth = 0;
1495     xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
1496     bool bResizeEquation = true;
1497     sal_Int32 nMaxIteration = 2;
1498     if ( bShowEquation )
1499     {
1500         OUString aXName, aYName;
1501         if ( !(xEquationProperties->getPropertyValue( u"XName"_ustr ) >>= aXName) )
1502             aXName = u"x"_ustr;
1503         if ( !(xEquationProperties->getPropertyValue( u"YName"_ustr ) >>= aYName) )
1504             aYName = u"f(x)"_ustr;
1505         xRegressionCurveCalculator->setXYNames( aXName, aYName );
1506     }
1507 
1508     for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
1509     {
1510         bResizeEquation = false;
1511         if( bShowEquation )
1512         {
1513             if (m_apNumberFormatterWrapper)
1514             {   // iteration 0: default representation (no wrap)
1515                 // iteration 1: expected width (nFormulaWidth) is calculated
1516                 aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
1517                     m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
1518                     nNumberFormatKey, nFormulaWidth );
1519                 nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
1520             }
1521             else
1522             {
1523                 aFormula = xRegressionCurveCalculator->getRepresentation();
1524             }
1525 
1526             if( bShowCorrCoeff )
1527             {
1528                 aFormula.append( "\n" );
1529             }
1530         }
1531         if( bShowCorrCoeff )
1532         {
1533             aFormula.append( "R" + OUStringChar( aSuperscriptFigures[2] )  + " = " );
1534             double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
1535             if (m_apNumberFormatterWrapper)
1536             {
1537                 Color nLabelCol;
1538                 bool bColChanged;
1539                 aFormula.append(
1540                     m_apNumberFormatterWrapper->getFormattedString(
1541                         nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
1542                 //@todo: change color of label if bColChanged is true
1543             }
1544             else
1545             {
1546                 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
1547                 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
1548                 sal_Unicode aDecimalSep = aNumDecimalSep[0];
1549                 aFormula.append( ::rtl::math::doubleToUString(
1550                                     fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
1551             }
1552         }
1553 
1554         awt::Point aScreenPosition2D;
1555         chart2::RelativePosition aRelativePosition;
1556         if( xEquationProperties->getPropertyValue( u"RelativePosition"_ustr) >>= aRelativePosition )
1557         {
1558             //@todo decide whether x is primary or secondary
1559             double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
1560             double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
1561             aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
1562             aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
1563         }
1564         else
1565             aScreenPosition2D = aDefaultPos;
1566 
1567         if( !aFormula.isEmpty())
1568         {
1569             // set fill and line properties on creation
1570             tNameSequence aNames;
1571             tAnySequence  aValues;
1572             PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
1573 
1574             rtl::Reference<SvxShapeText> xTextShape = ShapeFactory::createText(
1575                 xEquationTarget, aFormula.makeStringAndClear(),
1576                 aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
1577 
1578             ShapeFactory::setShapeName( xTextShape, rEquationCID );
1579             awt::Size aSize( xTextShape->getSize() );
1580             awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
1581                 aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
1582             //ensure that the equation is fully placed within the page (if possible)
1583             if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
1584                 aPos.X = m_aPageReferenceSize.Width - aSize.Width;
1585             if( aPos.X < 0 )
1586             {
1587                 aPos.X = 0;
1588                 if ( nFormulaWidth > 0 )
1589                 {
1590                     bResizeEquation = true;
1591                     if ( nCountIteration < nMaxIteration-1 )
1592                         xEquationTarget->remove( xTextShape );  // remove equation
1593                     nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
1594                     nFormulaWidth -= nCountIteration;
1595                     if ( nFormulaWidth < 0 )
1596                         nFormulaWidth = 0;
1597                 }
1598             }
1599             if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
1600                 aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
1601             if( aPos.Y < 0 )
1602                 aPos.Y = 0;
1603             if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
1604                 xTextShape->setPosition(aPos);  // if equation was not removed
1605         }
1606     }
1607 }
1608 
setTimeResolutionOnXAxis(tools::Long TimeResolution,const Date & rNullDate)1609 void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
1610 {
1611     m_nTimeResolution = TimeResolution;
1612     m_aNullDate = rNullDate;
1613 }
1614 
1615 // MinimumAndMaximumSupplier
calculateTimeResolutionOnXAxis()1616 tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
1617 {
1618     tools::Long nRet = css::chart::TimeUnit::YEAR;
1619     if (!m_pExplicitCategoriesProvider)
1620         return nRet;
1621 
1622     const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
1623     if (rDateCategories.empty())
1624         return nRet;
1625 
1626     std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
1627 
1628     aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
1629     if (aIt == aEnd)
1630         return nRet;
1631 
1632     Date aNullDate(30,12,1899);
1633     if (m_apNumberFormatterWrapper)
1634         aNullDate = m_apNumberFormatterWrapper->getNullDate();
1635 
1636     Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
1637     ++aIt;
1638     for(;aIt!=aEnd;++aIt)
1639     {
1640         if (std::isnan(*aIt))
1641             continue;
1642 
1643         Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
1644         if( nRet == css::chart::TimeUnit::YEAR )
1645         {
1646             if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
1647                 nRet = css::chart::TimeUnit::MONTH;
1648         }
1649         if( nRet == css::chart::TimeUnit::MONTH )
1650         {
1651             if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
1652                 nRet = css::chart::TimeUnit::DAY;
1653         }
1654         if( nRet == css::chart::TimeUnit::DAY )
1655             break;
1656         aPrevious=aCurrent;
1657     }
1658 
1659     return nRet;
1660 }
getMinimumX()1661 double VSeriesPlotter::getMinimumX()
1662 {
1663     double fMinimum, fMaximum;
1664     getMinimumAndMaximumX( fMinimum, fMaximum );
1665     return fMinimum;
1666 }
getMaximumX()1667 double VSeriesPlotter::getMaximumX()
1668 {
1669     double fMinimum, fMaximum;
1670     getMinimumAndMaximumX( fMinimum, fMaximum );
1671     return fMaximum;
1672 }
1673 
getMinimumAndMaximumYInRange(double fMinimumX,double fMaximumX,sal_Int32 nAxisIndex)1674 std::pair<double, double> VSeriesPlotter::getMinimumAndMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1675 {
1676     if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1677     {
1678         double fMinY, fMaxY;
1679         getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1680         return { fMinY, fMaxY };
1681     }
1682 
1683     double fMinimum = std::numeric_limits<double>::infinity();
1684     double fMaximum = -std::numeric_limits<double>::infinity();
1685     for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
1686     {
1687         for(VDataSeriesGroup & rXSlot : rXSlots)
1688         {
1689             double fLocalMinimum, fLocalMaximum;
1690             rXSlot.calculateYMinAndMaxForCategoryRange(
1691                                 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1692                                 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1693                                 , isSeparateStackingForDifferentSigns( 1 )
1694                                 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1695             if(fMaximum<fLocalMaximum)
1696                 fMaximum=fLocalMaximum;
1697             if(fMinimum>fLocalMinimum)
1698                 fMinimum=fLocalMinimum;
1699         }
1700     }
1701     if(std::isinf(fMinimum))
1702         fMinimum = std::numeric_limits<double>::quiet_NaN();
1703     if(std::isinf(fMaximum))
1704         fMaximum = std::numeric_limits<double>::quiet_NaN();
1705     return { fMinimum, fMaximum };
1706 }
1707 
getMinimumZ()1708 double VSeriesPlotter::getMinimumZ()
1709 {
1710     //this is the default for all charts without a meaningful z axis
1711     return 1.0;
1712 }
getMaximumZ()1713 double VSeriesPlotter::getMaximumZ()
1714 {
1715     if( m_nDimension!=3 || m_aZSlots.empty() )
1716         return getMinimumZ()+1;
1717     return m_aZSlots.size();
1718 }
1719 
1720 namespace
1721 {
lcl_isValueAxis(sal_Int32 nDimensionIndex,bool bCategoryXAxis)1722     bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
1723     {
1724         // default implementation: true for Y axes, and for value X axis
1725         if( nDimensionIndex == 0 )
1726             return !bCategoryXAxis;
1727         return nDimensionIndex == 1;
1728     }
1729 }
1730 
isExpandBorderToIncrementRhythm(sal_Int32 nDimensionIndex)1731 bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
1732 {
1733     return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1734 }
1735 
isExpandIfValuesCloseToBorder(sal_Int32 nDimensionIndex)1736 bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
1737 {
1738     // do not expand axes in 3D charts
1739     return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1740 }
1741 
isExpandWideValuesToZero(sal_Int32 nDimensionIndex)1742 bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
1743 {
1744     // default implementation: only for Y axis
1745     return nDimensionIndex == 1;
1746 }
1747 
isExpandNarrowValuesTowardZero(sal_Int32 nDimensionIndex)1748 bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
1749 {
1750     // default implementation: only for Y axis
1751     return nDimensionIndex == 1;
1752 }
1753 
isSeparateStackingForDifferentSigns(sal_Int32 nDimensionIndex)1754 bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
1755 {
1756     // default implementation: only for Y axis
1757     return nDimensionIndex == 1;
1758 }
1759 
getMinimumAndMaximumX(double & rfMinimum,double & rfMaximum) const1760 void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1761 {
1762     rfMinimum = std::numeric_limits<double>::infinity();
1763     rfMaximum = -std::numeric_limits<double>::infinity();
1764 
1765     for (auto const& ZSlot : m_aZSlots)
1766     {
1767         for (auto const& XSlot : ZSlot)
1768         {
1769             double fLocalMinimum, fLocalMaximum;
1770             XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
1771             if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
1772                 rfMinimum = fLocalMinimum;
1773             if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
1774                 rfMaximum = fLocalMaximum;
1775         }
1776     }
1777     if(std::isinf(rfMinimum))
1778         rfMinimum = std::numeric_limits<double>::quiet_NaN();
1779     if(std::isinf(rfMaximum))
1780         rfMaximum = std::numeric_limits<double>::quiet_NaN();
1781 }
1782 
getMinimumAndMaximumYInContinuousXRange(double & rfMinY,double & rfMaxY,double fMinX,double fMaxX,sal_Int32 nAxisIndex) const1783 void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
1784 {
1785     rfMinY = std::numeric_limits<double>::infinity();
1786     rfMaxY = -std::numeric_limits<double>::infinity();
1787 
1788     for (auto const& ZSlot : m_aZSlots)
1789     {
1790         for (auto const& XSlot : ZSlot)
1791         {
1792             double fLocalMinimum, fLocalMaximum;
1793             XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
1794             if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
1795                 rfMinY = fLocalMinimum;
1796             if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
1797                 rfMaxY = fLocalMaximum;
1798         }
1799     }
1800     if(std::isinf(rfMinY))
1801         rfMinY = std::numeric_limits<double>::quiet_NaN();
1802     if(std::isinf(rfMaxY))
1803         rfMaxY = std::numeric_limits<double>::quiet_NaN();
1804 }
1805 
getPointCount() const1806 sal_Int32 VSeriesPlotter::getPointCount() const
1807 {
1808     sal_Int32 nRet = 0;
1809 
1810     for (auto const& ZSlot : m_aZSlots)
1811     {
1812         for (auto const& XSlot : ZSlot)
1813         {
1814             sal_Int32 nPointCount = XSlot.getPointCount();
1815             if( nPointCount>nRet )
1816                 nRet = nPointCount;
1817         }
1818     }
1819     return nRet;
1820 }
1821 
setNumberFormatsSupplier(const uno::Reference<util::XNumberFormatsSupplier> & xNumFmtSupplier)1822 void VSeriesPlotter::setNumberFormatsSupplier(
1823     const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
1824 {
1825     m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
1826 }
1827 
setColorScheme(const uno::Reference<XColorScheme> & xColorScheme)1828 void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
1829 {
1830     m_xColorScheme = xColorScheme;
1831 }
1832 
setExplicitCategoriesProvider(ExplicitCategoriesProvider * pExplicitCategoriesProvider)1833 void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
1834 {
1835     m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
1836 }
1837 
getPointCount() const1838 sal_Int32 VDataSeriesGroup::getPointCount() const
1839 {
1840     if(!m_bMaxPointCountDirty)
1841         return m_nMaxPointCount;
1842 
1843     sal_Int32 nRet = 0;
1844 
1845     for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1846     {
1847         sal_Int32 nPointCount = pSeries->getTotalPointCount();
1848         if( nPointCount>nRet )
1849             nRet = nPointCount;
1850     }
1851     m_nMaxPointCount=nRet;
1852     m_aListOfCachedYValues.clear();
1853     m_aListOfCachedYValues.resize(m_nMaxPointCount);
1854     m_bMaxPointCountDirty=false;
1855     return nRet;
1856 }
1857 
getAttachedAxisIndexForFirstSeries() const1858 sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1859 {
1860     sal_Int32 nRet = 0;
1861 
1862     if (!m_aSeriesVector.empty())
1863         nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
1864 
1865     return nRet;
1866 }
1867 
getMinimumAndMaximumX(double & rfMinimum,double & rfMaximum) const1868 void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1869 {
1870 
1871     rfMinimum = std::numeric_limits<double>::infinity();
1872     rfMaximum = -std::numeric_limits<double>::infinity();
1873 
1874     for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1875     {
1876         sal_Int32 nPointCount = pSeries->getTotalPointCount();
1877         for(sal_Int32 nN=0;nN<nPointCount;nN++)
1878         {
1879             double fX = pSeries->getXValue( nN );
1880             if( std::isnan(fX) )
1881                 continue;
1882             if(rfMaximum<fX)
1883                 rfMaximum=fX;
1884             if(rfMinimum>fX)
1885                 rfMinimum=fX;
1886         }
1887     }
1888     if(std::isinf(rfMinimum))
1889         rfMinimum = std::numeric_limits<double>::quiet_NaN();
1890     if(std::isinf(rfMaximum))
1891         rfMaximum = std::numeric_limits<double>::quiet_NaN();
1892 }
1893 
1894 namespace {
1895 
1896 /**
1897  * Keep track of minimum and maximum Y values for one or more data series.
1898  * When multiple data series exist, that indicates that the data series are
1899  * stacked.
1900  *
1901  * <p>For each X value, we calculate separate Y value ranges for each data
1902  * series in the first pass.  In the second pass, we calculate the minimum Y
1903  * value by taking the absolute minimum value of all data series, whereas
1904  * the maximum Y value is the sum of all the series maximum Y values.</p>
1905  *
1906  * <p>Once that's done for all X values, the final min / max Y values get
1907  * calculated by taking the absolute min / max Y values across all the X
1908  * values.</p>
1909  */
1910 class PerXMinMaxCalculator
1911 {
1912     typedef std::pair<double, double> MinMaxType;
1913     typedef std::vector<MinMaxType> SeriesMinMaxType;
1914     typedef std::map<double, SeriesMinMaxType> GroupMinMaxType;
1915     GroupMinMaxType m_SeriesGroup;
1916     size_t mnNumSeries;
1917     size_t mnCurSeries;
1918 
1919 public:
PerXMinMaxCalculator(size_t numSeries)1920     PerXMinMaxCalculator(size_t numSeries) : mnNumSeries(numSeries), mnCurSeries(0) {}
1921 
nextSeries()1922     void nextSeries() { ++mnCurSeries; }
1923 
setValue(double fX,double fY)1924     void setValue(double fX, double fY)
1925     {
1926         SeriesMinMaxType& rStore = m_SeriesGroup[fX]; // get storage for given X value.
1927         if (rStore.empty())
1928             rStore.resize(mnNumSeries, { std::numeric_limits<double>::max(), std::numeric_limits<double>::min() });
1929 
1930         MinMaxType& r = rStore[mnCurSeries];
1931         if (fY < r.first)
1932             r.first = fY;
1933         if (r.second < fY)
1934             r.second = fY;
1935     }
1936 
getTotalRange(double & rfMin,double & rfMax) const1937     void getTotalRange(double& rfMin, double& rfMax) const
1938     {
1939         if (m_SeriesGroup.empty())
1940         {
1941             rfMin = std::numeric_limits<double>::quiet_NaN();
1942             rfMax = std::numeric_limits<double>::quiet_NaN();
1943             return;
1944         }
1945 
1946         rfMin = std::numeric_limits<double>::max();
1947         rfMax = std::numeric_limits<double>::min();
1948 
1949         /**
1950          * For each, X value, calculate Y value range
1951          */
1952         for (auto const& it : m_SeriesGroup)
1953         {
1954             const SeriesMinMaxType& rSeries = it.second;
1955             MinMaxType aPerXMinMax { std::numeric_limits<double>::max(), 0.0 };
1956             for (MinMaxType const& series : rSeries)
1957             {
1958                 double fYMin = series.first, fYMax = series.second;
1959                 if (fYMin < aPerXMinMax.first)
1960                     aPerXMinMax.first = fYMin; // min y-value
1961 
1962                 aPerXMinMax.second += fYMax; // accumulative max y-value.
1963             }
1964 
1965             if (rfMin > aPerXMinMax.first)
1966                 rfMin = aPerXMinMax.first;
1967             if (rfMax < aPerXMinMax.second)
1968                 rfMax = aPerXMinMax.second;
1969         }
1970     }
1971 
1972 };
1973 
1974 }
1975 
getMinimumAndMaximumYInContinuousXRange(double & rfMinY,double & rfMaxY,double fMinX,double fMaxX,sal_Int32 nAxisIndex) const1976 void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
1977     double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
1978 {
1979     rfMinY = std::numeric_limits<double>::quiet_NaN();
1980     rfMaxY = std::numeric_limits<double>::quiet_NaN();
1981 
1982     if (m_aSeriesVector.empty())
1983         // No data series.  Bail out.
1984         return;
1985 
1986     size_t nNumSeries = 0;
1987     for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
1988     {
1989         if (!pSeries)
1990             continue;
1991         if (nAxisIndex != pSeries->getAttachedAxisIndex())
1992             continue;
1993         nNumSeries++;
1994     }
1995 
1996     PerXMinMaxCalculator aRangeCalc(nNumSeries);
1997     for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
1998     {
1999         if (!pSeries)
2000             continue;
2001         if (nAxisIndex != pSeries->getAttachedAxisIndex())
2002             continue;
2003 
2004         for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
2005         {
2006             double fX = pSeries->getXValue(i);
2007             if (std::isnan(fX))
2008                 continue;
2009 
2010             if (fX < fMinX || fX > fMaxX)
2011                 // Outside specified X range.  Skip it.
2012                 continue;
2013 
2014             double fY = pSeries->getYValue(i);
2015             if (std::isnan(fY))
2016                 continue;
2017 
2018             aRangeCalc.setValue(fX, fY);
2019         }
2020         aRangeCalc.nextSeries();
2021     }
2022 
2023     aRangeCalc.getTotalRange(rfMinY, rfMaxY);
2024 }
2025 
calculateYMinAndMaxForCategory(sal_Int32 nCategoryIndex,bool bSeparateStackingForDifferentSigns,double & rfMinimumY,double & rfMaximumY,sal_Int32 nAxisIndex) const2026 void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
2027         , bool bSeparateStackingForDifferentSigns
2028         , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const
2029 {
2030     assert(nCategoryIndex >= 0);
2031     assert(nCategoryIndex < getPointCount());
2032 
2033     rfMinimumY = std::numeric_limits<double>::infinity();
2034     rfMaximumY = -std::numeric_limits<double>::infinity();
2035 
2036     if(m_aSeriesVector.empty())
2037         return;
2038 
2039     CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
2040     if( !aCachedYValues.m_bValuesDirty )
2041     {
2042         //return cached values
2043         rfMinimumY = aCachedYValues.m_fMinimumY;
2044         rfMaximumY = aCachedYValues.m_fMaximumY;
2045         return;
2046     }
2047 
2048     double fTotalSum = std::numeric_limits<double>::quiet_NaN();
2049     double fPositiveSum = std::numeric_limits<double>::quiet_NaN();
2050     double fNegativeSum = std::numeric_limits<double>::quiet_NaN();
2051     double fFirstPositiveY = std::numeric_limits<double>::quiet_NaN();
2052     double fFirstNegativeY = std::numeric_limits<double>::quiet_NaN();
2053 
2054     if( bSeparateStackingForDifferentSigns )
2055     {
2056         for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2057         {
2058             if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2059                 continue;
2060 
2061             double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2062             double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2063 
2064             if( fValueMaxY >= 0 )
2065             {
2066                 if( std::isnan( fPositiveSum ) )
2067                     fPositiveSum = fFirstPositiveY = fValueMaxY;
2068                 else
2069                     fPositiveSum += fValueMaxY;
2070             }
2071             if( fValueMinY < 0 )
2072             {
2073                 if(std::isnan( fNegativeSum ))
2074                     fNegativeSum = fFirstNegativeY = fValueMinY;
2075                 else
2076                     fNegativeSum += fValueMinY;
2077             }
2078         }
2079         rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
2080         rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
2081     }
2082     else
2083     {
2084         for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2085         {
2086             if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2087                 continue;
2088 
2089             double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2090             double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2091 
2092             if( std::isnan( fTotalSum ) )
2093             {
2094                 rfMinimumY = fValueMinY;
2095                 rfMaximumY = fTotalSum = fValueMaxY;
2096             }
2097             else
2098             {
2099                 fTotalSum += fValueMaxY;
2100                 if( rfMinimumY > fTotalSum )
2101                     rfMinimumY = fTotalSum;
2102                 if( rfMaximumY < fTotalSum )
2103                     rfMaximumY = fTotalSum;
2104             }
2105         }
2106     }
2107 
2108     aCachedYValues.m_fMinimumY = rfMinimumY;
2109     aCachedYValues.m_fMaximumY = rfMaximumY;
2110     aCachedYValues.m_bValuesDirty = false;
2111     m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
2112 }
2113 
calculateYMinAndMaxForCategoryRange(sal_Int32 nStartCategoryIndex,sal_Int32 nEndCategoryIndex,bool bSeparateStackingForDifferentSigns,double & rfMinimumY,double & rfMaximumY,sal_Int32 nAxisIndex)2114 void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
2115         sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
2116         , bool bSeparateStackingForDifferentSigns
2117         , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2118 {
2119     //@todo maybe cache these values
2120     rfMinimumY = std::numeric_limits<double>::infinity();
2121     rfMaximumY = -std::numeric_limits<double>::infinity();
2122 
2123     //iterate through the given categories
2124     if(nStartCategoryIndex<0)
2125         nStartCategoryIndex=0;
2126     const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
2127     if(nPointCount <= 0)
2128         return;
2129     if (nEndCategoryIndex >= nPointCount)
2130         nEndCategoryIndex = nPointCount - 1;
2131     if(nEndCategoryIndex<0)
2132         nEndCategoryIndex=0;
2133     for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
2134     {
2135         double fMinimumY = std::numeric_limits<double>::quiet_NaN();
2136         double fMaximumY = std::numeric_limits<double>::quiet_NaN();
2137 
2138         calculateYMinAndMaxForCategory( nCatIndex
2139             , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
2140 
2141         if(rfMinimumY > fMinimumY)
2142             rfMinimumY = fMinimumY;
2143         if(rfMaximumY < fMaximumY)
2144             rfMaximumY = fMaximumY;
2145     }
2146 }
2147 
getTransformedDepth() const2148 double VSeriesPlotter::getTransformedDepth() const
2149 {
2150     double MinZ = m_pMainPosHelper->getLogicMinZ();
2151     double MaxZ = m_pMainPosHelper->getLogicMaxZ();
2152     m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
2153     m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
2154     return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
2155 }
2156 
addSecondaryValueScale(const ExplicitScaleData & rScale,sal_Int32 nAxisIndex)2157 void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
2158 {
2159     if( nAxisIndex<1 )
2160         return;
2161 
2162     m_aSecondaryValueScales[nAxisIndex]=rScale;
2163 }
2164 
getPlottingPositionHelper(sal_Int32 nAxisIndex) const2165 PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
2166 {
2167     PlottingPositionHelper* pRet = nullptr;
2168     if(nAxisIndex>0)
2169     {
2170         tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
2171         if( aPosIt != m_aSecondaryPosHelperMap.end() )
2172         {
2173             pRet = aPosIt->second.get();
2174         }
2175         else if (m_pPosHelper)
2176         {
2177             tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
2178             if( aScaleIt != m_aSecondaryValueScales.end() )
2179             {
2180                 m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
2181                 pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
2182             }
2183         }
2184     }
2185     if( !pRet )
2186         pRet = m_pMainPosHelper;
2187     pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
2188     return *pRet;
2189 }
2190 
rearrangeLabelToAvoidOverlapIfRequested(const awt::Size &)2191 void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
2192 {
2193 }
2194 
getFirstSeries() const2195 VDataSeries* VSeriesPlotter::getFirstSeries() const
2196 {
2197     for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
2198     {
2199         if (!rGroup.empty())
2200         {
2201             if (!rGroup[0].m_aSeriesVector.empty())
2202             {
2203                 VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
2204                 if (pSeries)
2205                     return pSeries;
2206             }
2207         }
2208     }
2209     return nullptr;
2210 }
2211 
getCategoryName(sal_Int32 nPointIndex) const2212 OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
2213 {
2214     if (m_pExplicitCategoriesProvider)
2215     {
2216         Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
2217         if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
2218         {
2219             return aCategories[nPointIndex];
2220         }
2221     }
2222     return OUString();
2223 }
2224 
2225 namespace {
2226 // The following it to support rendering order for combo charts. A chart type
2227 // with a lower rendering order is rendered before (i.e., behind) a chart with a
2228 // higher rendering order. The rendering orders are based on rough guesses about
2229 // how much one chart (type) will obscure another chart (type). The intent is to
2230 // minimize obscuring of data, by putting charts that generally cover more
2231 // pixels (e.g., area charts) behind ones that generally cover fewer (e.g., line
2232 // charts).
2233 struct ROrderPair
2234 {
ROrderPairchart::__anon6eff89540511::ROrderPair2235     ROrderPair(OUString n, sal_Int32 r) : chartName(std::move(n)), renderOrder(r) {}
2236 
2237     OUString chartName;
2238     sal_Int32 renderOrder;
2239 };
2240 
2241 const ROrderPair pairList[] = {
2242     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_AREA, 0),
2243     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BAR, 6),   // bar & column are same
2244     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_COLUMN, 6),
2245     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_HISTOGRAM, 9),
2246     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_LINE, 8),
2247     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER, 5),
2248     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_PIE, 1),
2249     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_NET, 3),
2250     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET, 2),
2251     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK, 7),
2252     ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE, 4)
2253 };
2254 } // unnamed
2255 
getRenderOrder() const2256 sal_Int32 VSeriesPlotter::getRenderOrder() const
2257 {
2258     OUString aChartType = m_xChartTypeModel->getChartType();
2259     for (const auto& elem : pairList) {
2260         if (aChartType.equalsIgnoreAsciiCase(elem.chartName)) {
2261             return elem.renderOrder;
2262         }
2263     }
2264     SAL_WARN("chart2", "Unsupported chart type in getRenderOrder()");
2265     return 0;
2266 }
2267 
getAllSeries() const2268 std::vector<VDataSeries const*> VSeriesPlotter::getAllSeries() const
2269 {
2270     std::vector<VDataSeries const*> aAllSeries;
2271     for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
2272     {
2273         for(VDataSeriesGroup const & rGroup : rXSlot)
2274         {
2275             for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
2276                 aAllSeries.push_back(p.get());
2277         }
2278     }
2279     return aAllSeries;
2280 }
2281 
2282 
getAllSeries()2283 std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
2284 {
2285     std::vector<VDataSeries*> aAllSeries;
2286     for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
2287     {
2288         for(VDataSeriesGroup const & rGroup : rXSlot)
2289         {
2290             for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
2291                 aAllSeries.push_back(p.get());
2292         }
2293     }
2294     return aAllSeries;
2295 }
2296 
getSeriesNames() const2297 uno::Sequence<OUString> VSeriesPlotter::getSeriesNames() const
2298 {
2299     std::vector<OUString> aRetVector;
2300 
2301     OUString aRole;
2302     if (m_xChartTypeModel.is())
2303         aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2304 
2305     for (auto const& rGroup : m_aZSlots)
2306     {
2307         if (!rGroup.empty())
2308         {
2309             VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
2310             if (!rSeriesGroup.m_aSeriesVector.empty())
2311             {
2312                 VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
2313                 rtl::Reference< DataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
2314                 if( xSeries.is() )
2315                 {
2316                     OUString aSeriesName( xSeries->getLabelForRole( aRole ) );
2317                     aRetVector.push_back( aSeriesName );
2318                 }
2319             }
2320         }
2321     }
2322     return comphelper::containerToSequence( aRetVector );
2323 }
2324 
getAllSeriesNames() const2325 uno::Sequence<OUString> VSeriesPlotter::getAllSeriesNames() const
2326 {
2327     std::vector<OUString> aRetVector;
2328 
2329     OUString aRole;
2330     if (m_xChartTypeModel.is())
2331         aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2332 
2333     for (VDataSeries const* pSeries : getAllSeries())
2334     {
2335         if (pSeries)
2336         {
2337             OUString aSeriesName(pSeries->getModel()->getLabelForRole(aRole));
2338             aRetVector.push_back(aSeriesName);
2339         }
2340     }
2341     return comphelper::containerToSequence(aRetVector);
2342 }
2343 
setPageReferenceSize(const css::awt::Size & rPageRefSize)2344 void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
2345 {
2346     m_aPageReferenceSize = rPageRefSize;
2347 
2348     // set reference size also at all data series
2349 
2350     for (auto const & outer : m_aZSlots)
2351         for (VDataSeriesGroup const & rGroup : outer)
2352         {
2353             for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2354             {
2355                 pSeries->setPageReferenceSize(m_aPageReferenceSize);
2356             }
2357         }
2358 }
2359 
2360 //better performance for big data
setCoordinateSystemResolution(const Sequence<sal_Int32> & rCoordinateSystemResolution)2361 void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
2362 {
2363     m_aCoordinateSystemResolution = rCoordinateSystemResolution;
2364 }
2365 
WantToPlotInFrontOfAxisLine()2366 bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
2367 {
2368     return m_xChartTypeModel->isSeriesInFrontOfAxisLine();
2369 }
2370 
shouldSnapRectToUsedArea()2371 bool VSeriesPlotter::shouldSnapRectToUsedArea()
2372 {
2373     return m_nDimension != 3;
2374 }
2375 
createLegendEntries(const awt::Size & rEntryKeyAspectRatio,LegendPosition eLegendPosition,const Reference<beans::XPropertySet> & xTextProperties,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const Reference<uno::XComponentContext> & xContext,ChartModel & rModel)2376 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
2377               const awt::Size& rEntryKeyAspectRatio
2378             , LegendPosition eLegendPosition
2379             , const Reference< beans::XPropertySet >& xTextProperties
2380             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
2381             , const Reference< uno::XComponentContext >& xContext
2382             , ChartModel& rModel
2383             )
2384 {
2385     std::vector< ViewLegendEntry > aResult;
2386 
2387     if( xTarget.is() )
2388     {
2389         rtl::Reference< Diagram > xDiagram = rModel.getFirstChartDiagram();
2390         rtl::Reference< BaseCoordinateSystem > xCooSys(xDiagram->getBaseCoordinateSystems()[0]);
2391         bool bSwapXAndY = false;
2392 
2393         try
2394         {
2395             xCooSys->getPropertyValue( u"SwapXAndYAxis"_ustr ) >>= bSwapXAndY;
2396         }
2397         catch( const uno::Exception& )
2398         {
2399         }
2400 
2401         //iterate through all series
2402         bool bBreak = false;
2403         bool bFirstSeries = true;
2404 
2405 
2406         for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
2407         {
2408             for (VDataSeriesGroup const & rGroup : rGroupVector)
2409             {
2410                 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2411                 {
2412                     if (!pSeries)
2413                         continue;
2414 
2415                     // "ShowLegendEntry"
2416                     if (!pSeries->getModel()->getFastPropertyValue(PROP_DATASERIES_SHOW_LEGEND_ENTRY).get<sal_Bool>())
2417                     {
2418                         continue;
2419                     }
2420 
2421                     std::vector<ViewLegendEntry> aSeriesEntries(
2422                             createLegendEntriesForSeries(
2423                                         rEntryKeyAspectRatio, *pSeries, xTextProperties,
2424                                         xTarget, xContext));
2425 
2426                     //add series entries to the result now
2427 
2428                     // use only the first series if VaryColorsByPoint is set for the first series
2429                     if (bFirstSeries && pSeries->isVaryColorsByPoint())
2430                         bBreak = true;
2431                     bFirstSeries = false;
2432 
2433                     // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
2434                     // If the legend is top or bottom and we have a stacked bar-chart the normal order
2435                     // is the correct one, unless the chart type is horizontal bar-chart.
2436                     bool bReverse = false;
2437                     if ( bSwapXAndY )
2438                     {
2439                         StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2440                         bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
2441                     }
2442                     else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
2443                     {
2444                         StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2445                         bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
2446                     }
2447 
2448                     if (bReverse)
2449                         aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
2450                     else
2451                         aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
2452                 }
2453                 if (bBreak)
2454                     return aResult;
2455             }
2456         }
2457     }
2458 
2459     return aResult;
2460 }
2461 
createSymbols(const awt::Size & rEntryKeyAspectRatio,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const Reference<uno::XComponentContext> & xContext)2462 std::vector<ViewLegendSymbol> VSeriesPlotter::createSymbols(const awt::Size& rEntryKeyAspectRatio
2463             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
2464             , const Reference<uno::XComponentContext>& xContext)
2465 {
2466     std::vector<ViewLegendSymbol> aResult;
2467 
2468     if( xTarget.is() )
2469     {
2470         bool bBreak = false;
2471         bool bFirstSeries = true;
2472 
2473         for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
2474         {
2475             for (VDataSeriesGroup const & rGroup : rGroupVector)
2476             {
2477                 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2478                 {
2479                     if (!pSeries)
2480                         continue;
2481 
2482                     std::vector<ViewLegendSymbol> aSeriesSymbols = createSymbolsForSeries(rEntryKeyAspectRatio, *pSeries, xTarget, xContext);
2483 
2484                     //add series entries to the result now
2485 
2486                     // use only the first series if VaryColorsByPoint is set for the first series
2487                     if (bFirstSeries && pSeries->isVaryColorsByPoint())
2488                         bBreak = true;
2489 
2490                     bFirstSeries = false;
2491 
2492                     aResult.insert(aResult.end(), aSeriesSymbols.begin(), aSeriesSymbols.end());
2493                 }
2494                 if (bBreak)
2495                     return aResult;
2496             }
2497         }
2498     }
2499 
2500     return aResult;
2501 }
2502 
2503 namespace
2504 {
lcl_HasVisibleLine(const uno::Reference<beans::XPropertySet> & xProps,bool & rbHasDashedLine)2505 bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
2506 {
2507     bool bHasVisibleLine = false;
2508     rbHasDashedLine = false;
2509     drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
2510     if( xProps.is() && ( xProps->getPropertyValue( u"LineStyle"_ustr) >>= aLineStyle ) )
2511     {
2512         if( aLineStyle != drawing::LineStyle_NONE )
2513             bHasVisibleLine = true;
2514         if( aLineStyle == drawing::LineStyle_DASH )
2515             rbHasDashedLine = true;
2516     }
2517     return bHasVisibleLine;
2518 }
2519 
lcl_HasRegressionCurves(const VDataSeries & rSeries,bool & rbHasDashedLine)2520 bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
2521 {
2522     bool bHasRegressionCurves = false;
2523     const rtl::Reference< DataSeries >& xRegrCont( rSeries.getModel() );
2524     for( const rtl::Reference< RegressionCurveModel > & rCurve : xRegrCont->getRegressionCurves2() )
2525     {
2526         bHasRegressionCurves = true;
2527         lcl_HasVisibleLine( rCurve, rbHasDashedLine );
2528     }
2529     return bHasRegressionCurves;
2530 }
2531 }
getLegendSymbolStyle()2532 LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
2533 {
2534     return LegendSymbolStyle::Box;
2535 }
2536 
getPreferredLegendKeyAspectRatio()2537 awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
2538 {
2539     awt::Size aRet(1000,1000);
2540     if( m_nDimension==3 )
2541         return aRet;
2542 
2543     bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
2544     bool bHasLines = false;
2545     bool bHasDashedLines = false;
2546     //iterate through all series
2547     for (VDataSeries* pSeries :  getAllSeries())
2548     {
2549         if( bSeriesAllowsLines )
2550         {
2551             bool bCurrentDashed = false;
2552             if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
2553             {
2554                 bHasLines = true;
2555                 if( bCurrentDashed )
2556                 {
2557                     bHasDashedLines = true;
2558                     break;
2559                 }
2560             }
2561         }
2562         bool bRegressionHasDashedLines=false;
2563         if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
2564         {
2565             bHasLines = true;
2566             if( bRegressionHasDashedLines )
2567             {
2568                 bHasDashedLines = true;
2569                 break;
2570             }
2571         }
2572     }
2573     if( bHasLines )
2574     {
2575         if( bHasDashedLines )
2576             aRet = awt::Size(1600,-1);
2577         else
2578             aRet = awt::Size(800,-1);
2579     }
2580     return aRet;
2581 }
2582 
getExplicitSymbol(const VDataSeries &,sal_Int32)2583 uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
2584 {
2585     return uno::Any();
2586 }
2587 
createLegendSymbolForSeries(const awt::Size & rEntryKeyAspectRatio,const VDataSeries & rSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)2588 rtl::Reference<SvxShapeGroup> VSeriesPlotter::createLegendSymbolForSeries(
2589                   const awt::Size& rEntryKeyAspectRatio
2590                 , const VDataSeries& rSeries
2591                 , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
2592 {
2593 
2594     LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2595     uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
2596 
2597     VLegendSymbolFactory::PropertyType ePropType =
2598         VLegendSymbolFactory::PropertyType::FilledSeries;
2599 
2600     // todo: maybe the property-style does not solely depend on the
2601     // legend-symbol type
2602     switch( eLegendSymbolStyle )
2603     {
2604         case LegendSymbolStyle::Line:
2605             ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2606             break;
2607         default:
2608             break;
2609     }
2610     rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2611         xTarget, eLegendSymbolStyle,
2612         rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol );
2613 
2614     return xShape;
2615 }
2616 
createLegendSymbolForPoint(const awt::Size & rEntryKeyAspectRatio,const VDataSeries & rSeries,sal_Int32 nPointIndex,const rtl::Reference<SvxShapeGroupAnyD> & xTarget)2617 rtl::Reference< SvxShapeGroup > VSeriesPlotter::createLegendSymbolForPoint(
2618                   const awt::Size& rEntryKeyAspectRatio
2619                 , const VDataSeries& rSeries
2620                 , sal_Int32 nPointIndex
2621                 , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
2622 {
2623 
2624     LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2625     uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
2626 
2627     VLegendSymbolFactory::PropertyType ePropType =
2628         VLegendSymbolFactory::PropertyType::FilledSeries;
2629 
2630     // todo: maybe the property-style does not solely depend on the
2631     // legend-symbol type
2632     switch( eLegendSymbolStyle )
2633     {
2634         case LegendSymbolStyle::Line:
2635             ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2636             break;
2637         default:
2638             break;
2639     }
2640 
2641     // the default properties for the data point are the data series properties.
2642     // If a data point has own attributes overwrite them
2643     const Reference< beans::XPropertySet >& xSeriesProps( rSeries.getPropertiesOfSeries() );
2644     Reference< beans::XPropertySet > xPointSet( xSeriesProps );
2645     if( rSeries.isAttributedDataPoint( nPointIndex ) )
2646         xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
2647 
2648     // if a data point has no own color use a color from the diagram's color scheme
2649     if( ! rSeries.hasPointOwnColor( nPointIndex ))
2650     {
2651         Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
2652         if( xCloneable.is() && m_xColorScheme.is() )
2653         {
2654             xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
2655             Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
2656             if( xChild.is())
2657                 xChild->setParent( xSeriesProps );
2658 
2659             OSL_ASSERT( xPointSet.is());
2660             xPointSet->setPropertyValue(
2661                 u"Color"_ustr, uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
2662         }
2663     }
2664 
2665     rtl::Reference< SvxShapeGroup > xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2666         xTarget, eLegendSymbolStyle, xPointSet, ePropType, aExplicitSymbol );
2667 
2668     return xShape;
2669 }
2670 
createLegendEntriesForSeries(const awt::Size & rEntryKeyAspectRatio,const VDataSeries & rSeries,const Reference<beans::XPropertySet> & xTextProperties,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const Reference<uno::XComponentContext> & xContext)2671 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
2672               const awt::Size& rEntryKeyAspectRatio
2673             , const VDataSeries& rSeries
2674             , const Reference< beans::XPropertySet >& xTextProperties
2675             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
2676             , const Reference< uno::XComponentContext >& xContext
2677             )
2678 {
2679     std::vector< ViewLegendEntry > aResult;
2680 
2681     if( ! ( xTarget.is() && xContext.is() ) )
2682         return aResult;
2683 
2684     try
2685     {
2686         ViewLegendEntry aEntry;
2687         OUString aLabelText;
2688         bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
2689         bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
2690             CHART2_SERVICE_NAME_CHARTTYPE_PIE);
2691         try
2692         {
2693             if (bIsPie)
2694             {
2695                 bool bDonut = false;
2696                 // "UseRings"
2697                 if ((m_xChartTypeModel->getFastPropertyValue(PROP_PIECHARTTYPE_USE_RINGS) >>= bDonut) && bDonut)
2698                     bIsPie = false;
2699             }
2700         }
2701         catch (const uno::Exception&)
2702         {
2703         }
2704 
2705         if (bVaryColorsByPoint || bIsPie)
2706         {
2707             Sequence< OUString > aCategoryNames;
2708             if( m_pExplicitCategoriesProvider )
2709                 aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
2710             Sequence<sal_Int32> deletedLegendEntries;
2711             try
2712             {
2713                 // "DeletedLegendEntries"
2714                 rSeries.getModel()->getFastPropertyValue(PROP_DATASERIES_DELETED_LEGEND_ENTRIES) >>= deletedLegendEntries;
2715             }
2716             catch (const uno::Exception&)
2717             {
2718             }
2719             for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
2720             {
2721                 bool deletedLegendEntry = false;
2722                 for (const auto& deletedLegendEntryIdx : deletedLegendEntries)
2723                 {
2724                     if (nIdx == deletedLegendEntryIdx)
2725                     {
2726                         deletedLegendEntry = true;
2727                         break;
2728                     }
2729                 }
2730                 if (deletedLegendEntry)
2731                     continue;
2732 
2733                 // symbol
2734                 rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
2735 
2736                 // create the symbol
2737                 rtl::Reference< SvxShapeGroup > xShape = createLegendSymbolForPoint( rEntryKeyAspectRatio,
2738                     rSeries, nIdx, xSymbolGroup );
2739 
2740                 // set CID to symbol for selection
2741                 if( xShape.is() )
2742                 {
2743                     aEntry.xSymbol = std::move(xSymbolGroup);
2744 
2745                     OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
2746                     aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2747                     OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2748                     ShapeFactory::setShapeName( xShape, aCID );
2749                 }
2750 
2751                 // label
2752                 aLabelText = aCategoryNames[nIdx];
2753                 if( xShape.is() || !aLabelText.isEmpty() )
2754                 {
2755                     aEntry.xLabel = FormattedStringHelper::createFormattedString( aLabelText, xTextProperties );
2756                     aResult.push_back(aEntry);
2757                 }
2758             }
2759         }
2760         else
2761         {
2762             // symbol
2763             rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
2764 
2765             // create the symbol
2766             rtl::Reference<SvxShapeGroup> xShape = createLegendSymbolForSeries(
2767                 rEntryKeyAspectRatio, rSeries, xSymbolGroup );
2768 
2769             // set CID to symbol for selection
2770             if( xShape.is())
2771             {
2772                 aEntry.xSymbol = std::move(xSymbolGroup);
2773 
2774                 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2775                 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2776                 ShapeFactory::setShapeName( xShape, aCID );
2777             }
2778 
2779             // label
2780             aLabelText = rSeries.getModel()->getLabelForRole( m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : u"values-y"_ustr);
2781             aEntry.xLabel = FormattedStringHelper::createFormattedString( aLabelText, xTextProperties );
2782 
2783             aResult.push_back(aEntry);
2784         }
2785 
2786         // don't show legend entry of regression curve & friends if this type of chart
2787         // doesn't support statistics #i63016#, fdo#37197
2788         if (!m_xChartTypeModel->isSupportingStatisticProperties(m_nDimension))
2789             return aResult;
2790 
2791         const rtl::Reference< DataSeries >& xRegrCont = rSeries.getModel();
2792         if( xRegrCont.is())
2793         {
2794             const std::vector< rtl::Reference< RegressionCurveModel > > & aCurves = xRegrCont->getRegressionCurves2();
2795             sal_Int32 i = 0, nCount = aCurves.size();
2796             for( i=0; i<nCount; ++i )
2797             {
2798                 //label
2799                 OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
2800                 replaceParamterInString( aResStr, u"%SERIESNAME", aLabelText );
2801                 aEntry.xLabel = FormattedStringHelper::createFormattedString( aResStr, xTextProperties );
2802 
2803                 // symbol
2804                 rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
2805 
2806                 // create the symbol
2807                 rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2808                     xSymbolGroup, LegendSymbolStyle::Line,
2809                     aCurves[i],
2810                     VLegendSymbolFactory::PropertyType::Line, uno::Any() );
2811 
2812                 // set CID to symbol for selection
2813                 if( xShape.is())
2814                 {
2815                     aEntry.xSymbol = std::move(xSymbolGroup);
2816 
2817                     bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
2818                     ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
2819                     OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
2820                     aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2821                     OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2822                     ShapeFactory::setShapeName( xShape, aCID );
2823                 }
2824 
2825                 aResult.push_back(aEntry);
2826             }
2827         }
2828     }
2829     catch( const uno::Exception & )
2830     {
2831         DBG_UNHANDLED_EXCEPTION("chart2" );
2832     }
2833     return aResult;
2834 }
2835 
createSymbolsForSeries(const awt::Size & rEntryKeyAspectRatio,const VDataSeries & rSeries,const rtl::Reference<SvxShapeGroupAnyD> & xTarget,const Reference<uno::XComponentContext> & xContext)2836 std::vector<ViewLegendSymbol> VSeriesPlotter::createSymbolsForSeries(
2837               const awt::Size& rEntryKeyAspectRatio
2838             , const VDataSeries& rSeries
2839             , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
2840             , const Reference<uno::XComponentContext>& xContext)
2841 {
2842     std::vector<ViewLegendSymbol> aResult;
2843 
2844     if (!(xTarget.is() && xContext.is()))
2845         return aResult;
2846 
2847     try
2848     {
2849         ViewLegendSymbol aEntry;
2850         // symbol
2851         rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D(xTarget));
2852 
2853         // create the symbol
2854         rtl::Reference<SvxShapeGroup> xShape = createLegendSymbolForSeries(rEntryKeyAspectRatio, rSeries, xSymbolGroup );
2855 
2856         // set CID to symbol for selection
2857         if (xShape.is())
2858         {
2859             aEntry.xSymbol = std::move(xSymbolGroup);
2860             aResult.push_back(aEntry);
2861         }
2862     }
2863     catch (const uno::Exception &)
2864     {
2865         DBG_UNHANDLED_EXCEPTION("chart2" );
2866     }
2867     return aResult;
2868 }
2869 
createSeriesPlotter(const rtl::Reference<ChartType> & xChartTypeModel,sal_Int32 nDimensionCount,bool bExcludingPositioning)2870 VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
2871     const rtl::Reference<ChartType>& xChartTypeModel
2872     , sal_Int32 nDimensionCount
2873     , bool bExcludingPositioning )
2874 {
2875     if (!xChartTypeModel.is())
2876         return nullptr;
2877 
2878     OUString aChartType = xChartTypeModel->getChartType();
2879 
2880     VSeriesPlotter* pRet=nullptr;
2881     if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
2882         pRet = new BarChart(xChartTypeModel,nDimensionCount);
2883     else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
2884         pRet = new BarChart(xChartTypeModel,nDimensionCount);
2885     else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_HISTOGRAM ) )
2886         pRet = new HistogramChart(xChartTypeModel, nDimensionCount);
2887     else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
2888         pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
2889     else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
2890         pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
2891     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
2892         pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2893     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
2894         pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
2895     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
2896         pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
2897     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
2898         pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
2899     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
2900         pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
2901     else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
2902         pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
2903     else
2904         pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2905     return pRet;
2906 }
2907 
2908 } //namespace chart
2909 
2910 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2911