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