1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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>
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 <ChartTypeHelper.hxx>
41 #include <Clipping.hxx>
42 #include <servicenames_charttypes.hxx>
43 #include <NumberFormatterWrapper.hxx>
44 #include <DataSeriesHelper.hxx>
45 #include <RegressionCurveModel.hxx>
46 #include <RegressionCurveHelper.hxx>
47 #include <VLegendSymbolFactory.hxx>
48 #include <FormattedStringHelper.hxx>
49 #include <RelativePositionHelper.hxx>
50 #include <DateHelper.hxx>
51 #include <DiagramHelper.hxx>
52 #include <defines.hxx>
53 #include <ChartModel.hxx>
55 //only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
56 #include "BarChart.hxx"
57 #include "HistogramChart.hxx"
58 #include "PieChart.hxx"
59 #include "AreaChart.hxx"
60 #include "CandleStickChart.hxx"
61 #include "BubbleChart.hxx"
62 #include "NetChart.hxx"
63 #include <unonames.hxx>
64 #include <SpecialCharacters.hxx>
66 #include <com/sun/star/chart2/DataPointLabel.hpp>
67 #include <com/sun/star/chart/ErrorBarStyle.hpp>
68 #include <com/sun/star/chart/TimeUnit.hpp>
69 #include <com/sun/star/chart2/MovingAverageType.hpp>
70 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
71 #include <com/sun/star/container/XChild.hpp>
72 #include <com/sun/star/chart2/RelativePosition.hpp>
73 #include <o3tl/safeint.hxx>
74 #include <tools/color.hxx>
75 #include <tools/UnitConversion.hxx>
76 #include <rtl/ustrbuf.hxx>
77 #include <rtl/math.hxx>
78 #include <basegfx/vector/b2dvector.hxx>
79 #include <com/sun/star/drawing/LineStyle.hpp>
80 #include <com/sun/star/util/XCloneable.hpp>
82 #include <unotools/localedatawrapper.hxx>
83 #include <comphelper/sequence.hxx>
85 #include <vcl/svapp.hxx>
86 #include <vcl/settings.hxx>
87 #include <comphelper/diagnose_ex.hxx>
88 #include <sal/log.hxx>
92 #include <unordered_map>
97 using namespace ::com::sun::star
;
98 using namespace ::com::sun::star::chart
;
99 using namespace ::com::sun::star::chart2
;
100 using namespace ::chart::DataSeriesProperties
;
101 using ::com::sun::star::uno::Reference
;
102 using ::com::sun::star::uno::Sequence
;
104 VDataSeriesGroup::CachedYValues::CachedYValues()
105 : m_bValuesDirty(true)
111 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr
<VDataSeries
> pSeries
)
113 , m_bMaxPointCountDirty(true)
114 , m_nMaxPointCount(0)
116 m_aSeriesVector
[0] = std::move(pSeries
);
119 VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup
&& other
) noexcept
120 : m_aSeriesVector( std::move(other
.m_aSeriesVector
) )
121 , m_bMaxPointCountDirty( other
.m_bMaxPointCountDirty
)
122 , m_nMaxPointCount( other
.m_nMaxPointCount
)
123 , m_aListOfCachedYValues( std::move(other
.m_aListOfCachedYValues
) )
127 VDataSeriesGroup::~VDataSeriesGroup()
131 void VDataSeriesGroup::deleteSeries()
133 //delete all data series help objects:
134 m_aSeriesVector
.clear();
137 void VDataSeriesGroup::addSeries( std::unique_ptr
<VDataSeries
> pSeries
)
139 m_aSeriesVector
.push_back(std::move(pSeries
));
140 m_bMaxPointCountDirty
=true;
143 sal_Int32
VDataSeriesGroup::getSeriesCount() const
145 return m_aSeriesVector
.size();
148 VSeriesPlotter::VSeriesPlotter( rtl::Reference
<ChartType
> xChartTypeModel
149 , sal_Int32 nDimensionCount
, bool bCategoryXAxis
)
150 : PlotterBase( nDimensionCount
)
151 , m_pMainPosHelper( nullptr )
152 , m_xChartTypeModel(std::move(xChartTypeModel
))
153 , m_bCategoryXAxis(bCategoryXAxis
)
154 , m_nTimeResolution(css::chart::TimeUnit::DAY
)
155 , m_aNullDate(30,12,1899)
156 , m_pExplicitCategoriesProvider(nullptr)
157 , m_bPointsWereSkipped(false)
158 , m_bPieLabelsAllowToMove(false)
159 , m_aAvailableOuterRect(0, 0, 0, 0)
161 SAL_WARN_IF(!m_xChartTypeModel
.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
164 VSeriesPlotter::~VSeriesPlotter()
166 //delete all data series help objects:
167 for (std::vector
<VDataSeriesGroup
> & rGroupVector
: m_aZSlots
)
169 for (VDataSeriesGroup
& rGroup
: rGroupVector
)
171 rGroup
.deleteSeries();
173 rGroupVector
.clear();
177 m_aSecondaryPosHelperMap
.clear();
179 m_aSecondaryValueScales
.clear();
182 void VSeriesPlotter::addSeries( std::unique_ptr
<VDataSeries
> pSeries
, sal_Int32 zSlot
, sal_Int32 xSlot
, sal_Int32 ySlot
)
184 //take ownership of pSeries
186 OSL_PRECOND( pSeries
, "series to add is NULL" );
192 if( m_pExplicitCategoriesProvider
&& m_pExplicitCategoriesProvider
->isDateAxis() )
193 pSeries
->setXValues( m_pExplicitCategoriesProvider
->getOriginalCategories() );
195 pSeries
->setCategoryXAxis();
199 if( m_pExplicitCategoriesProvider
)
200 pSeries
->setXValuesIfNone( m_pExplicitCategoriesProvider
->getOriginalCategories() );
203 if(zSlot
<0 || o3tl::make_unsigned(zSlot
)>=m_aZSlots
.size())
206 std::vector
< VDataSeriesGroup
> aZSlot
;
207 aZSlot
.emplace_back( std::move(pSeries
) );
208 m_aZSlots
.push_back( std::move(aZSlot
) );
213 std::vector
< VDataSeriesGroup
>& rXSlots
= m_aZSlots
[zSlot
];
215 if(xSlot
<0 || o3tl::make_unsigned(xSlot
)>=rXSlots
.size())
217 //append the series to already existing x series
218 rXSlots
.emplace_back( std::move(pSeries
) );
222 //x slot is already occupied
223 //y slot decides what to do:
225 VDataSeriesGroup
& rYSlots
= rXSlots
[xSlot
];
226 sal_Int32 nYSlotCount
= rYSlots
.getSeriesCount();
230 //move all existing series in the xSlot to next slot
232 OSL_FAIL( "Not implemented yet");
234 else if( ySlot
== -1 || ySlot
>= nYSlotCount
)
236 //append the series to already existing y series
237 rYSlots
.addSeries( std::move(pSeries
) );
241 //y slot is already occupied
242 //insert at given y and x position
245 OSL_FAIL( "Not implemented yet");
251 drawing::Direction3D
VSeriesPlotter::getPreferredDiagramAspectRatio() const
253 drawing::Direction3D
aRet(1.0,1.0,1.0);
257 drawing::Direction3D
aScale( m_pPosHelper
->getScaledLogicWidth() );
258 aRet
.DirectionZ
= aScale
.DirectionZ
*0.2;
259 if(aRet
.DirectionZ
>1.0)
261 if(aRet
.DirectionZ
>10)
266 void VSeriesPlotter::releaseShapes()
268 for (std::vector
<VDataSeriesGroup
> const & rGroupVector
: m_aZSlots
)
270 for (VDataSeriesGroup
const & rGroup
: rGroupVector
)
272 //iterate through all series in this x slot
273 for (std::unique_ptr
<VDataSeries
> const & pSeries
: rGroup
.m_aSeriesVector
)
275 pSeries
->releaseShapes();
281 rtl::Reference
<SvxShapeGroupAnyD
> VSeriesPlotter::getSeriesGroupShape( VDataSeries
* pDataSeries
282 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
284 if( !pDataSeries
->m_xGroupShape
)
285 //create a group shape for this series and add to logic target:
286 pDataSeries
->m_xGroupShape
= createGroupShape( xTarget
,pDataSeries
->getCID() );
287 return pDataSeries
->m_xGroupShape
;
290 rtl::Reference
<SvxShapeGroupAnyD
> VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries
* pDataSeries
291 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
293 if(!pDataSeries
->m_xFrontSubGroupShape
)
295 //ensure that the series group shape is already created
296 rtl::Reference
<SvxShapeGroupAnyD
> xSeriesShapes( getSeriesGroupShape( pDataSeries
, xTarget
) );
297 //ensure that the back child is created first
298 getSeriesGroupShapeBackChild( pDataSeries
, xTarget
);
299 //use series group shape as parent for the new created front group shape
300 pDataSeries
->m_xFrontSubGroupShape
= createGroupShape( xSeriesShapes
);
302 return pDataSeries
->m_xFrontSubGroupShape
;
305 rtl::Reference
<SvxShapeGroupAnyD
> VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries
* pDataSeries
306 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
308 if(!pDataSeries
->m_xBackSubGroupShape
)
310 //ensure that the series group shape is already created
311 rtl::Reference
<SvxShapeGroupAnyD
> xSeriesShapes( getSeriesGroupShape( pDataSeries
, xTarget
) );
312 //use series group shape as parent for the new created back group shape
313 pDataSeries
->m_xBackSubGroupShape
= createGroupShape( xSeriesShapes
);
315 return pDataSeries
->m_xBackSubGroupShape
;
318 rtl::Reference
<SvxShapeGroup
> VSeriesPlotter::getLabelsGroupShape( VDataSeries
& rDataSeries
319 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTextTarget
)
321 //xTextTarget needs to be a 2D shape container always!
322 if(!rDataSeries
.m_xLabelsGroupShape
)
324 //create a 2D group shape for texts of this series and add to text target:
325 rDataSeries
.m_xLabelsGroupShape
= ShapeFactory::createGroup2D( xTextTarget
, rDataSeries
.getLabelsCID() );
327 return rDataSeries
.m_xLabelsGroupShape
;
330 rtl::Reference
<SvxShapeGroupAnyD
> VSeriesPlotter::getErrorBarsGroupShape( VDataSeries
& rDataSeries
331 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
334 rtl::Reference
<SvxShapeGroupAnyD
> &rShapeGroup
=
335 bYError
? rDataSeries
.m_xErrorYBarsGroupShape
: rDataSeries
.m_xErrorXBarsGroupShape
;
339 //create a group shape for this series and add to logic target:
340 rShapeGroup
= createGroupShape( xTarget
,rDataSeries
.getErrorBarsCID(bYError
) );
346 OUString
VSeriesPlotter::getLabelTextForValue( VDataSeries
const & rDataSeries
347 , sal_Int32 nPointIndex
349 , bool bAsPercentage
)
353 if (m_apNumberFormatterWrapper
)
355 sal_Int32 nNumberFormatKey
= 0;
356 if( rDataSeries
.hasExplicitNumberFormat(nPointIndex
,bAsPercentage
) )
357 nNumberFormatKey
= rDataSeries
.getExplicitNumberFormat(nPointIndex
,bAsPercentage
);
358 else if( bAsPercentage
)
360 sal_Int32 nPercentFormat
= DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper
->getNumberFormatsSupplier() );
361 if( nPercentFormat
!= -1 )
362 nNumberFormatKey
= nPercentFormat
;
366 nNumberFormatKey
= rDataSeries
.detectNumberFormatKey( nPointIndex
);
368 if(nNumberFormatKey
<0)
373 aNumber
= m_apNumberFormatterWrapper
->getFormattedString(
374 nNumberFormatKey
, fValue
, nLabelCol
, bColChanged
);
375 //@todo: change color of label if bColChanged is true
379 const LocaleDataWrapper
& rLocaleDataWrapper
= Application::GetSettings().GetLocaleDataWrapper();
380 const OUString
& aNumDecimalSep
= rLocaleDataWrapper
.getNumDecimalSep();
381 assert(aNumDecimalSep
.getLength() > 0);
382 sal_Unicode cDecSeparator
= aNumDecimalSep
[0];
383 aNumber
= ::rtl::math::doubleToUString( fValue
, rtl_math_StringFormat_G
/*rtl_math_StringFormat*/
384 , 3/*DecPlaces*/ , cDecSeparator
);
389 rtl::Reference
<SvxShapeText
> VSeriesPlotter::createDataLabel( const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
390 , VDataSeries
& rDataSeries
391 , sal_Int32 nPointIndex
394 , const awt::Point
& rScreenPosition2D
395 , LabelAlignment eAlignment
397 , sal_Int32 nTextWidth
)
399 rtl::Reference
<SvxShapeText
> xTextShape
;
400 Sequence
<uno::Reference
<XDataPointCustomLabelField
>> aCustomLabels
;
404 const uno::Reference
< css::beans::XPropertySet
> xPropertySet(
405 rDataSeries
.getPropertiesOfPoint( nPointIndex
) );
406 if( xPropertySet
.is() )
408 uno::Any aAny
= xPropertySet
->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS
);
409 if( aAny
.hasValue() )
411 aAny
>>= aCustomLabels
;
415 awt::Point
aScreenPosition2D(rScreenPosition2D
);
416 if(eAlignment
==LABEL_ALIGN_LEFT
)
417 aScreenPosition2D
.X
-= nOffset
;
418 else if(eAlignment
==LABEL_ALIGN_RIGHT
)
419 aScreenPosition2D
.X
+= nOffset
;
420 else if(eAlignment
==LABEL_ALIGN_TOP
)
421 aScreenPosition2D
.Y
-= nOffset
;
422 else if(eAlignment
==LABEL_ALIGN_BOTTOM
)
423 aScreenPosition2D
.Y
+= nOffset
;
425 rtl::Reference
<SvxShapeGroup
> xTarget_
=
426 ShapeFactory::createGroup2D(
427 getLabelsGroupShape(rDataSeries
, xTarget
),
428 ObjectIdentifier::createPointCID( rDataSeries
.getLabelCID_Stub(), nPointIndex
));
430 //check whether the label needs to be created and how:
431 DataPointLabel
* pLabel
= rDataSeries
.getDataPointLabelIfLabel( nPointIndex
);
436 //prepare legend symbol
438 // get the font size for the label through the "CharHeight" property
439 // attached to the passed data series entry.
440 // (By tracing font height values it results that for pie chart the
441 // font size is not the same for all labels, but here no font size
442 // modification occurs).
443 float fViewFontSize( 10.0 );
445 uno::Reference
< beans::XPropertySet
> xProps( rDataSeries
.getPropertiesOfPoint( nPointIndex
) );
447 xProps
->getPropertyValue( u
"CharHeight"_ustr
) >>= fViewFontSize
;
448 fViewFontSize
= convertPointToMm100(fViewFontSize
);
451 // the font height is used for computing the size of an optional legend
452 // symbol to be prepended to the text label.
453 rtl::Reference
< SvxShapeGroup
> xSymbol
;
454 if(pLabel
->ShowLegendSymbol
)
456 sal_Int32 nSymbolHeight
= static_cast< sal_Int32
>( fViewFontSize
* 0.6 );
457 awt::Size aCurrentRatio
= getPreferredLegendKeyAspectRatio();
458 sal_Int32 nSymbolWidth
= aCurrentRatio
.Width
;
459 if( aCurrentRatio
.Height
> 0 )
461 nSymbolWidth
= nSymbolHeight
* aCurrentRatio
.Width
/aCurrentRatio
.Height
;
463 awt::Size
aMaxSymbolExtent( nSymbolWidth
, nSymbolHeight
);
465 if( rDataSeries
.isVaryColorsByPoint() )
466 xSymbol
= VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent
, rDataSeries
, nPointIndex
, xTarget_
);
468 xSymbol
= VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent
, rDataSeries
, xTarget_
);
472 bool bTextWrap
= false;
473 OUString
aSeparator(u
" "_ustr
);
474 double fRotationDegrees
= 0.0;
477 uno::Reference
< beans::XPropertySet
> xPointProps( rDataSeries
.getPropertiesOfPoint( nPointIndex
) );
480 xPointProps
->getPropertyValue( u
"TextWordWrap"_ustr
) >>= bTextWrap
;
481 xPointProps
->getPropertyValue( u
"LabelSeparator"_ustr
) >>= aSeparator
;
482 // Extract the optional text rotation through the
483 // "TextRotation" property attached to the passed data point.
484 xPointProps
->getPropertyValue( u
"TextRotation"_ustr
) >>= fRotationDegrees
;
487 catch( const uno::Exception
& )
489 TOOLS_WARN_EXCEPTION("chart2", "" );
492 sal_Int32 nLineCountForSymbolsize
= 0;
493 sal_uInt32 nTextListLength
= 4;
494 sal_uInt32 nCustomLabelsCount
= aCustomLabels
.getLength();
495 Sequence
< OUString
> aTextList( nTextListLength
);
497 bool bUseCustomLabel
= nCustomLabelsCount
> 0;
498 if( bUseCustomLabel
)
500 nTextListLength
= ( nCustomLabelsCount
> 3 ) ? nCustomLabelsCount
: 3;
502 aTextList
= Sequence
< OUString
>( nTextListLength
);
503 auto pTextList
= aTextList
.getArray();
504 for( sal_uInt32 i
= 0; i
< nCustomLabelsCount
; ++i
)
506 switch( aCustomLabels
[i
]->getFieldType() )
508 case DataPointCustomLabelFieldType_VALUE
:
510 pTextList
[i
] = getLabelTextForValue( rDataSeries
, nPointIndex
, fValue
, false );
513 case DataPointCustomLabelFieldType_CATEGORYNAME
:
515 pTextList
[i
] = getCategoryName( nPointIndex
);
518 case DataPointCustomLabelFieldType_SERIESNAME
:
521 if ( m_xChartTypeModel
)
522 aRole
= m_xChartTypeModel
->getRoleOfSequenceForSeriesLabel();
523 const rtl::Reference
< DataSeries
>& xSeries( rDataSeries
.getModel() );
524 pTextList
[i
] = xSeries
->getLabelForRole( aRole
);
527 case DataPointCustomLabelFieldType_PERCENTAGE
:
535 pTextList
[i
] = getLabelTextForValue(rDataSeries
, nPointIndex
, fValue
, true);
538 case DataPointCustomLabelFieldType_CELLRANGE
:
540 if (aCustomLabels
[i
]->getDataLabelsRange())
541 pTextList
[i
] = aCustomLabels
[i
]->getString();
543 pTextList
[i
] = OUString();
546 case DataPointCustomLabelFieldType_CELLREF
:
548 // TODO: for now doesn't show placeholder
549 pTextList
[i
] = OUString();
552 case DataPointCustomLabelFieldType_TEXT
:
554 pTextList
[i
] = aCustomLabels
[i
]->getString();
557 case DataPointCustomLabelFieldType_NEWLINE
:
565 aCustomLabels
[i
]->setString( aTextList
[i
] );
570 auto pTextList
= aTextList
.getArray();
571 if( pLabel
->ShowCategoryName
)
573 pTextList
[0] = getCategoryName( nPointIndex
);
576 if( pLabel
->ShowSeriesName
)
579 if ( m_xChartTypeModel
)
580 aRole
= m_xChartTypeModel
->getRoleOfSequenceForSeriesLabel();
581 const rtl::Reference
< DataSeries
>& xSeries( rDataSeries
.getModel() );
582 pTextList
[1] = xSeries
->getLabelForRole( aRole
);
585 if( pLabel
->ShowNumber
)
587 pTextList
[2] = getLabelTextForValue(rDataSeries
, nPointIndex
, fValue
, false);
590 if( pLabel
->ShowNumberInPercent
)
598 pTextList
[3] = getLabelTextForValue(rDataSeries
, nPointIndex
, fValue
, true);
602 for (auto const& line
: aTextList
)
604 if( !line
.isEmpty() )
606 ++nLineCountForSymbolsize
;
610 //prepare properties for multipropertyset-interface of shape
611 tNameSequence
* pPropNames
;
612 tAnySequence
* pPropValues
;
613 if( !rDataSeries
.getTextLabelMultiPropertyLists( nPointIndex
, pPropNames
, pPropValues
) )
616 // set text alignment for the text shape to be created.
617 LabelPositionHelper::changeTextAdjustment( *pPropValues
, *pPropNames
, eAlignment
);
619 // check if data series entry percent value and absolute value have to
620 // be appended to the text label, and what should be the separator
621 // character (comma, space, new line). In case it is a new line we get
622 // a multi-line label.
623 bool bMultiLineLabel
= ( aSeparator
== "\n" );
625 if( bUseCustomLabel
)
627 Sequence
< uno::Reference
< XFormattedString
> > aFormattedLabels(
628 comphelper::containerToSequence
<uno::Reference
<XFormattedString
>>(aCustomLabels
));
631 xTextShape
= ShapeFactory::
632 createText( xTarget_
, aFormattedLabels
, *pPropNames
, *pPropValues
,
633 ShapeFactory::makeTransformation( aScreenPosition2D
) );
637 // join text list elements
638 OUStringBuffer aText
;
639 for( sal_uInt32 nN
= 0; nN
< nTextListLength
; ++nN
)
641 if( !aTextList
[nN
].isEmpty() )
643 if( !aText
.isEmpty() )
645 aText
.append(aSeparator
);
647 aText
.append( aTextList
[nN
] );
652 xTextShape
= ShapeFactory::
653 createText( xTarget_
, aText
.makeStringAndClear(), *pPropNames
, *pPropValues
,
654 ShapeFactory::makeTransformation( aScreenPosition2D
) );
657 if( !xTextShape
.is() )
660 // we need to use a default value for the maximum width property ?
661 if( nTextWidth
== 0 && bTextWrap
)
664 (m_aPageReferenceSize
.Height
< m_aPageReferenceSize
.Width
)
665 ? m_aPageReferenceSize
.Height
666 : m_aPageReferenceSize
.Width
;
667 nTextWidth
= nMinSize
/ 3;
670 // in case text must be wrapped set the maximum width property
671 // for the text shape
672 if( nTextWidth
!= 0 && bTextWrap
)
674 // compute the height of a line of text
675 if( !bMultiLineLabel
|| nLineCountForSymbolsize
<= 0 )
677 nLineCountForSymbolsize
= 1;
679 awt::Size aTextSize
= xTextShape
->getSize();
680 sal_Int32 aTextLineHeight
= aTextSize
.Height
/ nLineCountForSymbolsize
;
682 // set maximum text width
683 uno::Any
aTextMaximumFrameWidth( nTextWidth
);
684 xTextShape
->SvxShape::setPropertyValue( u
"TextMaximumFrameWidth"_ustr
, aTextMaximumFrameWidth
);
686 // compute the total lines of text
687 aTextSize
= xTextShape
->getSize();
688 nLineCountForSymbolsize
= aTextSize
.Height
/ aTextLineHeight
;
691 // in case text is rotated, the transformation property of the text
692 // shape is modified.
693 if( fRotationDegrees
!= 0.0 )
695 const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees
) );
696 xTextShape
->SvxShape::setPropertyValue( u
"Transformation"_ustr
, ShapeFactory::makeTransformation( aScreenPosition2D
, fDegreesPi
) );
697 LabelPositionHelper::correctPositionForRotation( xTextShape
, eAlignment
, fRotationDegrees
, true /*bRotateAroundCenter*/ );
700 awt::Point
aTextShapePos(xTextShape
->getPosition());
701 if( m_bPieLabelsAllowToMove
&& rDataSeries
.isLabelCustomPos(nPointIndex
) )
703 awt::Point aRelPos
= rDataSeries
.getLabelPosition(aTextShapePos
, nPointIndex
);
704 if( aRelPos
.X
!= -1 )
706 xTextShape
->setPosition(aRelPos
);
707 if( !m_xChartTypeModel
->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE
) &&
708 // "ShowCustomLeaderLines"
709 rDataSeries
.getModel()->getFastPropertyValue( PROP_DATASERIES_SHOW_CUSTOM_LEADERLINES
).get
<sal_Bool
>())
711 const basegfx::B2IRectangle
aRect(
712 BaseGFXHelper::makeRectangle(aRelPos
, xTextShape
->getSize()));
713 sal_Int32 nX1
= rScreenPosition2D
.X
;
714 sal_Int32 nY1
= rScreenPosition2D
.Y
;
715 const sal_Int32 nX2
= std::clamp(nX1
, aRelPos
.X
, aRect
.getMaxX());
716 const sal_Int32 nY2
= std::clamp(nY1
, aRect
.getMinY(), aRect
.getMaxY());
718 //when the line is very short compared to the page size don't create one
719 ::basegfx::B2DVector
aLength(nX1
- nX2
, nY1
- nY2
);
720 double fPageDiagonaleLength
721 = std::hypot(m_aPageReferenceSize
.Width
, m_aPageReferenceSize
.Height
);
722 if ((aLength
.getLength() / fPageDiagonaleLength
) >= 0.01)
724 drawing::PointSequenceSequence aPoints
{ { {nX1
, nY1
}, {nX2
, nY2
} } };
726 VLineProperties aVLineProperties
;
727 ShapeFactory::createLine2D(xTarget
, aPoints
, &aVLineProperties
);
733 // in case legend symbol has to be displayed, text shape position is
737 awt::Point
aNewTextPos(xTextShape
->getPosition());
739 awt::Point
aSymbolPosition(aNewTextPos
);
740 awt::Size
aSymbolSize( xSymbol
->getSize() );
741 awt::Size aTextSize
= xTextShape
->getSize();
743 sal_Int32 nXDiff
= aSymbolSize
.Width
+ static_cast< sal_Int32
>( std::max( 100.0, fViewFontSize
* 0.22 ) );//minimum 1mm
744 if( !bMultiLineLabel
|| nLineCountForSymbolsize
<= 0 )
745 nLineCountForSymbolsize
= 1;
746 aSymbolPosition
.Y
+= ((aTextSize
.Height
/nLineCountForSymbolsize
)/4);
748 if(eAlignment
==LABEL_ALIGN_LEFT
749 || eAlignment
==LABEL_ALIGN_LEFT_TOP
750 || eAlignment
==LABEL_ALIGN_LEFT_BOTTOM
)
752 aSymbolPosition
.X
-= nXDiff
;
754 else if(eAlignment
==LABEL_ALIGN_RIGHT
755 || eAlignment
==LABEL_ALIGN_RIGHT_TOP
756 || eAlignment
==LABEL_ALIGN_RIGHT_BOTTOM
)
758 aNewTextPos
.X
+= nXDiff
;
760 else if(eAlignment
==LABEL_ALIGN_TOP
761 || eAlignment
==LABEL_ALIGN_BOTTOM
762 || eAlignment
==LABEL_ALIGN_CENTER
)
764 aSymbolPosition
.X
-= nXDiff
/2;
765 aNewTextPos
.X
+= nXDiff
/2;
768 xSymbol
->setPosition( aSymbolPosition
);
769 xTextShape
->setPosition( aNewTextPos
);
772 catch( const uno::Exception
& )
774 TOOLS_WARN_EXCEPTION("chart2", "" );
782 double lcl_getErrorBarLogicLength(
783 const uno::Sequence
< double > & rData
,
784 const uno::Reference
< beans::XPropertySet
>& xProp
,
785 sal_Int32 nErrorBarStyle
,
790 double fResult
= std::numeric_limits
<double>::quiet_NaN();
793 switch( nErrorBarStyle
)
795 case css::chart::ErrorBarStyle::NONE
:
797 case css::chart::ErrorBarStyle::VARIANCE
:
798 fResult
= StatisticsHelper::getVariance( rData
);
800 case css::chart::ErrorBarStyle::STANDARD_DEVIATION
:
801 fResult
= StatisticsHelper::getStandardDeviation( rData
);
803 case css::chart::ErrorBarStyle::RELATIVE
:
806 if( xProp
->getPropertyValue( bPositive
807 ? u
"PositiveError"_ustr
808 : u
"NegativeError"_ustr
) >>= fPercent
)
810 if( nIndex
>=0 && nIndex
< rData
.getLength() &&
811 ! std::isnan( rData
[nIndex
] ) &&
812 ! std::isnan( fPercent
))
814 fResult
= rData
[nIndex
] * fPercent
/ 100.0;
819 case css::chart::ErrorBarStyle::ABSOLUTE
:
820 xProp
->getPropertyValue( bPositive
821 ? u
"PositiveError"_ustr
822 : u
"NegativeError"_ustr
) >>= fResult
;
824 case css::chart::ErrorBarStyle::ERROR_MARGIN
:
826 // todo: check if this is really what's called error-margin
828 if( xProp
->getPropertyValue( bPositive
829 ? u
"PositiveError"_ustr
830 : u
"NegativeError"_ustr
) >>= fPercent
)
832 double fMaxValue
= -std::numeric_limits
<double>::infinity();
833 for(double d
: rData
)
838 if( std::isfinite( fMaxValue
) &&
839 std::isfinite( fPercent
))
841 fResult
= fMaxValue
* fPercent
/ 100.0;
846 case css::chart::ErrorBarStyle::STANDARD_ERROR
:
847 fResult
= StatisticsHelper::getStandardError( rData
);
849 case css::chart::ErrorBarStyle::FROM_DATA
:
851 uno::Reference
< chart2::data::XDataSource
> xErrorBarData( xProp
, uno::UNO_QUERY
);
852 if( xErrorBarData
.is())
853 fResult
= StatisticsHelper::getErrorFromDataSource(
854 xErrorBarData
, nIndex
, bPositive
, bYError
);
859 catch( const uno::Exception
& )
861 TOOLS_WARN_EXCEPTION("chart2", "" );
867 void lcl_AddErrorBottomLine( const drawing::Position3D
& rPosition
, ::basegfx::B2DVector aMainDirection
868 , std::vector
<std::vector
<css::drawing::Position3D
>>& rPoly
, sal_Int32 nSequenceIndex
)
870 double fFixedWidth
= 200.0;
872 aMainDirection
.normalize();
873 ::basegfx::B2DVector
aOrthoDirection(-aMainDirection
.getY(),aMainDirection
.getX());
874 aOrthoDirection
.normalize();
876 ::basegfx::B2DVector
aAnchor( rPosition
.PositionX
, rPosition
.PositionY
);
877 ::basegfx::B2DVector aStart
= aAnchor
+ aOrthoDirection
*fFixedWidth
/2.0;
878 ::basegfx::B2DVector aEnd
= aAnchor
- aOrthoDirection
*fFixedWidth
/2.0;
880 AddPointToPoly( rPoly
, drawing::Position3D( aStart
.getX(), aStart
.getY(), rPosition
.PositionZ
), nSequenceIndex
);
881 AddPointToPoly( rPoly
, drawing::Position3D( aEnd
.getX(), aEnd
.getY(), rPosition
.PositionZ
), nSequenceIndex
);
884 ::basegfx::B2DVector
lcl_getErrorBarMainDirection(
885 const drawing::Position3D
& rStart
886 , const drawing::Position3D
& rBottomEnd
887 , PlottingPositionHelper
const * pPosHelper
888 , const drawing::Position3D
& rUnscaledLogicPosition
891 ::basegfx::B2DVector
aMainDirection( rStart
.PositionX
- rBottomEnd
.PositionX
892 , rStart
.PositionY
- rBottomEnd
.PositionY
);
893 if( !aMainDirection
.getLength() )
895 //get logic clip values:
896 double MinX
= pPosHelper
->getLogicMinX();
897 double MinY
= pPosHelper
->getLogicMinY();
898 double MaxX
= pPosHelper
->getLogicMaxX();
899 double MaxY
= pPosHelper
->getLogicMaxY();
900 double fZ
= pPosHelper
->getLogicMinZ();
904 //main direction has constant x value
905 MinX
= rUnscaledLogicPosition
.PositionX
;
906 MaxX
= rUnscaledLogicPosition
.PositionX
;
910 //main direction has constant y value
911 MinY
= rUnscaledLogicPosition
.PositionY
;
912 MaxY
= rUnscaledLogicPosition
.PositionY
;
915 drawing::Position3D aStart
= pPosHelper
->transformLogicToScene( MinX
, MinY
, fZ
, false );
916 drawing::Position3D aEnd
= pPosHelper
->transformLogicToScene( MaxX
, MaxY
, fZ
, false );
918 aMainDirection
= ::basegfx::B2DVector( aStart
.PositionX
- aEnd
.PositionX
919 , aStart
.PositionY
- aEnd
.PositionY
);
921 if( !aMainDirection
.getLength() )
925 return aMainDirection
;
928 drawing::Position3D
lcl_transformMixedToScene( PlottingPositionHelper
const * pPosHelper
929 , double fX
/*scaled*/, double fY
/*unscaled*/, double fZ
/*unscaled*/ )
932 return drawing::Position3D(0,0,0);
933 pPosHelper
->doLogicScaling( nullptr,&fY
,&fZ
);
934 pPosHelper
->clipScaledLogicValues( &fX
,&fY
,&fZ
);
935 return pPosHelper
->transformScaledLogicToScene( fX
, fY
, fZ
, false );
938 } // anonymous namespace
940 void VSeriesPlotter::createErrorBar(
941 const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
942 , const drawing::Position3D
& rUnscaledLogicPosition
943 , const uno::Reference
< beans::XPropertySet
> & xErrorBarProperties
944 , const VDataSeries
& rVDataSeries
946 , bool bYError
/* = true */
947 , const double* pfScaledLogicX
950 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel
, m_nDimension
) )
953 if( ! xErrorBarProperties
.is())
958 bool bShowPositive
= false;
959 bool bShowNegative
= false;
960 sal_Int32 nErrorBarStyle
= css::chart::ErrorBarStyle::VARIANCE
;
962 xErrorBarProperties
->getPropertyValue( u
"ShowPositiveError"_ustr
) >>= bShowPositive
;
963 xErrorBarProperties
->getPropertyValue( u
"ShowNegativeError"_ustr
) >>= bShowNegative
;
964 xErrorBarProperties
->getPropertyValue( u
"ErrorBarStyle"_ustr
) >>= nErrorBarStyle
;
966 if(!bShowPositive
&& !bShowNegative
)
969 if(nErrorBarStyle
==css::chart::ErrorBarStyle::NONE
)
975 drawing::Position3D
aUnscaledLogicPosition(rUnscaledLogicPosition
);
976 if(nErrorBarStyle
==css::chart::ErrorBarStyle::STANDARD_DEVIATION
)
979 aUnscaledLogicPosition
.PositionY
= rVDataSeries
.getYMeanValue();
981 aUnscaledLogicPosition
.PositionX
= rVDataSeries
.getXMeanValue();
984 bool bCreateNegativeBorder
= false;//make a vertical line at the negative end of the error bar
985 bool bCreatePositiveBorder
= false;//make a vertical line at the positive end of the error bar
986 drawing::Position3D
aMiddle(aUnscaledLogicPosition
);
987 const double fX
= aUnscaledLogicPosition
.PositionX
;
988 const double fY
= aUnscaledLogicPosition
.PositionY
;
989 const double fZ
= aUnscaledLogicPosition
.PositionZ
;
990 double fScaledX
= fX
;
992 fScaledX
= *pfScaledLogicX
;
994 m_pPosHelper
->doLogicScaling( &fScaledX
, nullptr, nullptr );
996 aMiddle
= lcl_transformMixedToScene( m_pPosHelper
, fScaledX
, fY
, fZ
);
998 drawing::Position3D
aNegative(aMiddle
);
999 drawing::Position3D
aPositive(aMiddle
);
1001 uno::Sequence
< double > aData( bYError
? rVDataSeries
.getAllY() : rVDataSeries
.getAllX() );
1005 double fLength
= lcl_getErrorBarLogicLength( aData
, xErrorBarProperties
, nErrorBarStyle
, nIndex
, true, bYError
);
1006 if( std::isfinite( fLength
) )
1008 double fLocalX
= fX
;
1009 double fLocalY
= fY
;
1013 aPositive
= lcl_transformMixedToScene( m_pPosHelper
, fScaledX
, fLocalY
, fZ
);
1018 aPositive
= m_pPosHelper
->transformLogicToScene( fLocalX
, fLocalY
, fZ
, true );
1020 bCreatePositiveBorder
= m_pPosHelper
->isLogicVisible(fLocalX
, fLocalY
, fZ
);
1023 bShowPositive
= false;
1028 double fLength
= lcl_getErrorBarLogicLength( aData
, xErrorBarProperties
, nErrorBarStyle
, nIndex
, false, bYError
);
1029 if( std::isfinite( fLength
) )
1031 double fLocalX
= fX
;
1032 double fLocalY
= fY
;
1036 aNegative
= lcl_transformMixedToScene( m_pPosHelper
, fScaledX
, fLocalY
, fZ
);
1041 aNegative
= m_pPosHelper
->transformLogicToScene( fLocalX
, fLocalY
, fZ
, true );
1043 if (std::isfinite(aNegative
.PositionX
) &&
1044 std::isfinite(aNegative
.PositionY
) &&
1045 std::isfinite(aNegative
.PositionZ
)) {
1046 bCreateNegativeBorder
= m_pPosHelper
->isLogicVisible( fLocalX
, fLocalY
, fZ
);
1048 // If error bars result in a numerical problem (e.g., an
1049 // error bar on a logarithmic chart that results in a point
1050 // <= 0) then just turn off the error bar.
1052 // TODO: This perhaps should display a warning, so the user
1053 // knows why a bar is not appearing.
1054 // TODO: This test could also be added to the positive case,
1055 // though a numerical overflow there is less likely.
1056 bShowNegative
= false;
1060 bShowNegative
= false;
1063 if(!bShowPositive
&& !bShowNegative
)
1066 std::vector
<std::vector
<css::drawing::Position3D
>> aPoly
;
1068 sal_Int32 nSequenceIndex
=0;
1070 AddPointToPoly( aPoly
, aNegative
, nSequenceIndex
);
1071 AddPointToPoly( aPoly
, aMiddle
, nSequenceIndex
);
1073 AddPointToPoly( aPoly
, aPositive
, nSequenceIndex
);
1075 if( bShowNegative
&& bCreateNegativeBorder
)
1077 ::basegfx::B2DVector aMainDirection
= lcl_getErrorBarMainDirection( aMiddle
, aNegative
, m_pPosHelper
, aUnscaledLogicPosition
, bYError
);
1079 lcl_AddErrorBottomLine( aNegative
, aMainDirection
, aPoly
, nSequenceIndex
);
1081 if( bShowPositive
&& bCreatePositiveBorder
)
1083 ::basegfx::B2DVector aMainDirection
= lcl_getErrorBarMainDirection( aMiddle
, aPositive
, m_pPosHelper
, aUnscaledLogicPosition
, bYError
);
1085 lcl_AddErrorBottomLine( aPositive
, aMainDirection
, aPoly
, nSequenceIndex
);
1088 rtl::Reference
<SvxShapePolyPolygon
> xShape
= ShapeFactory::createLine2D( xTarget
, aPoly
);
1089 PropertyMapper::setMappedProperties( *xShape
, xErrorBarProperties
, PropertyMapper::getPropertyNameMapForLineProperties() );
1091 catch( const uno::Exception
& )
1093 TOOLS_WARN_EXCEPTION("chart2", "" );
1098 void VSeriesPlotter::addErrorBorder(
1099 const drawing::Position3D
& rPos0
1100 ,const drawing::Position3D
& rPos1
1101 ,const rtl::Reference
<SvxShapeGroupAnyD
>& rTarget
1102 ,const uno::Reference
< beans::XPropertySet
>& rErrorBorderProp
)
1104 std::vector
<std::vector
<css::drawing::Position3D
>> aPoly
{ { rPos0
, rPos1
} };
1105 rtl::Reference
<SvxShapePolyPolygon
> xShape
= ShapeFactory::createLine2D(
1107 PropertyMapper::setMappedProperties( *xShape
, rErrorBorderProp
,
1108 PropertyMapper::getPropertyNameMapForLineProperties() );
1111 void VSeriesPlotter::createErrorRectangle(
1112 const drawing::Position3D
& rUnscaledLogicPosition
1113 ,VDataSeries
& rVDataSeries
1115 ,const rtl::Reference
<SvxShapeGroupAnyD
>& rTarget
1116 ,bool bUseXErrorData
1117 ,bool bUseYErrorData
)
1119 if ( m_nDimension
!= 2 )
1122 // error border properties
1123 Reference
< beans::XPropertySet
> xErrorBorderPropX
, xErrorBorderPropY
;
1124 if ( bUseXErrorData
)
1126 xErrorBorderPropX
= rVDataSeries
.getXErrorBarProperties( nIndex
);
1127 if ( !xErrorBorderPropX
.is() )
1130 rtl::Reference
<SvxShapeGroupAnyD
> xErrorBorder_ShapesX
=
1131 getErrorBarsGroupShape( rVDataSeries
, rTarget
, false );
1133 if ( bUseYErrorData
)
1135 xErrorBorderPropY
= rVDataSeries
.getYErrorBarProperties( nIndex
);
1136 if ( !xErrorBorderPropY
.is() )
1139 rtl::Reference
<SvxShapeGroupAnyD
> xErrorBorder_ShapesY
=
1140 getErrorBarsGroupShape( rVDataSeries
, rTarget
, true );
1142 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel
, m_nDimension
) )
1147 bool bShowXPositive
= false;
1148 bool bShowXNegative
= false;
1149 bool bShowYPositive
= false;
1150 bool bShowYNegative
= false;
1152 sal_Int32 nErrorBorderStyleX
= css::chart::ErrorBarStyle::VARIANCE
;
1153 sal_Int32 nErrorBorderStyleY
= css::chart::ErrorBarStyle::VARIANCE
;
1155 if ( bUseXErrorData
)
1157 xErrorBorderPropX
->getPropertyValue( u
"ErrorBarStyle"_ustr
) >>= nErrorBorderStyleX
;
1158 xErrorBorderPropX
->getPropertyValue( u
"ShowPositiveError"_ustr
) >>= bShowXPositive
;
1159 xErrorBorderPropX
->getPropertyValue( u
"ShowNegativeError"_ustr
) >>= bShowXNegative
;
1161 if ( bUseYErrorData
)
1163 xErrorBorderPropY
->getPropertyValue( u
"ErrorBarStyle"_ustr
) >>= nErrorBorderStyleY
;
1164 xErrorBorderPropY
->getPropertyValue( u
"ShowPositiveError"_ustr
) >>= bShowYPositive
;
1165 xErrorBorderPropY
->getPropertyValue( u
"ShowNegativeError"_ustr
) >>= bShowYNegative
;
1168 if ( bUseXErrorData
&& nErrorBorderStyleX
== css::chart::ErrorBarStyle::NONE
)
1169 bUseXErrorData
= false;
1170 if ( bUseYErrorData
&& nErrorBorderStyleY
== css::chart::ErrorBarStyle::NONE
)
1171 bUseYErrorData
= false;
1173 if ( !bShowXPositive
&& !bShowXNegative
&& !bShowYPositive
&& !bShowYNegative
)
1176 if ( !m_pPosHelper
)
1179 drawing::Position3D
aUnscaledLogicPosition( rUnscaledLogicPosition
);
1180 if ( bUseXErrorData
&& nErrorBorderStyleX
== css::chart::ErrorBarStyle::STANDARD_DEVIATION
)
1181 aUnscaledLogicPosition
.PositionX
= rVDataSeries
.getXMeanValue();
1182 if ( bUseYErrorData
&& nErrorBorderStyleY
== css::chart::ErrorBarStyle::STANDARD_DEVIATION
)
1183 aUnscaledLogicPosition
.PositionY
= rVDataSeries
.getYMeanValue();
1185 const double fX
= aUnscaledLogicPosition
.PositionX
;
1186 const double fY
= aUnscaledLogicPosition
.PositionY
;
1187 const double fZ
= aUnscaledLogicPosition
.PositionZ
;
1188 double fScaledX
= fX
;
1189 m_pPosHelper
->doLogicScaling( &fScaledX
, nullptr, nullptr );
1191 const uno::Sequence
< double >& aDataX( rVDataSeries
.getAllX() );
1192 const uno::Sequence
< double >& aDataY( rVDataSeries
.getAllY() );
1198 if ( bUseXErrorData
)
1200 if ( bShowXPositive
)
1201 fPosX
= lcl_getErrorBarLogicLength( aDataX
, xErrorBorderPropX
,
1202 nErrorBorderStyleX
, nIndex
, true, false );
1203 if ( bShowXNegative
)
1204 fNegX
= lcl_getErrorBarLogicLength( aDataX
, xErrorBorderPropX
,
1205 nErrorBorderStyleX
, nIndex
, false, false );
1208 if ( bUseYErrorData
)
1210 if ( bShowYPositive
)
1211 fPosY
= lcl_getErrorBarLogicLength( aDataY
, xErrorBorderPropY
,
1212 nErrorBorderStyleY
, nIndex
, true, true );
1213 if ( bShowYNegative
)
1214 fNegY
= lcl_getErrorBarLogicLength( aDataY
, xErrorBorderPropY
,
1215 nErrorBorderStyleY
, nIndex
, false, true );
1218 if ( !( std::isfinite( fPosX
) &&
1219 std::isfinite( fPosY
) &&
1220 std::isfinite( fNegX
) &&
1221 std::isfinite( fNegY
) ) )
1224 drawing::Position3D
aBottomLeft( lcl_transformMixedToScene( m_pPosHelper
,
1225 fX
- fNegX
, fY
- fNegY
, fZ
) );
1226 drawing::Position3D
aTopLeft( lcl_transformMixedToScene( m_pPosHelper
,
1227 fX
- fNegX
, fY
+ fPosY
, fZ
) );
1228 drawing::Position3D
aTopRight( lcl_transformMixedToScene( m_pPosHelper
,
1229 fX
+ fPosX
, fY
+ fPosY
, fZ
) );
1230 drawing::Position3D
aBottomRight( lcl_transformMixedToScene( m_pPosHelper
,
1231 fX
+ fPosX
, fY
- fNegY
, fZ
) );
1232 if ( bUseXErrorData
)
1235 addErrorBorder( aTopLeft
, aTopRight
, xErrorBorder_ShapesX
, xErrorBorderPropX
);
1238 addErrorBorder( aBottomRight
, aBottomLeft
, xErrorBorder_ShapesX
, xErrorBorderPropX
);
1241 if ( bUseYErrorData
)
1244 addErrorBorder( aBottomLeft
, aTopLeft
, xErrorBorder_ShapesY
, xErrorBorderPropY
);
1247 addErrorBorder( aTopRight
, aBottomRight
, xErrorBorder_ShapesY
, xErrorBorderPropY
);
1250 catch( const uno::Exception
& )
1252 DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
1256 void VSeriesPlotter::createErrorBar_X( const drawing::Position3D
& rUnscaledLogicPosition
1257 , VDataSeries
& rVDataSeries
, sal_Int32 nPointIndex
1258 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
1263 uno::Reference
< beans::XPropertySet
> xErrorBarProp(rVDataSeries
.getXErrorBarProperties(nPointIndex
));
1264 if( xErrorBarProp
.is())
1266 rtl::Reference
<SvxShapeGroupAnyD
> xErrorBarsGroup_Shapes
=
1267 getErrorBarsGroupShape(rVDataSeries
, xTarget
, false);
1269 createErrorBar( xErrorBarsGroup_Shapes
1270 , rUnscaledLogicPosition
, xErrorBarProp
1271 , rVDataSeries
, nPointIndex
1272 , false /* bYError */
1277 void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D
& rUnscaledLogicPosition
1278 , VDataSeries
& rVDataSeries
, sal_Int32 nPointIndex
1279 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
1280 , double const * pfScaledLogicX
)
1285 uno::Reference
< beans::XPropertySet
> xErrorBarProp(rVDataSeries
.getYErrorBarProperties(nPointIndex
));
1286 if( xErrorBarProp
.is())
1288 rtl::Reference
<SvxShapeGroupAnyD
> xErrorBarsGroup_Shapes
=
1289 getErrorBarsGroupShape(rVDataSeries
, xTarget
, true);
1291 createErrorBar( xErrorBarsGroup_Shapes
1292 , rUnscaledLogicPosition
, xErrorBarProp
1293 , rVDataSeries
, nPointIndex
1294 , true /* bYError */
1299 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries
const & rVDataSeries
,
1300 const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
,
1301 const rtl::Reference
<SvxShapeGroupAnyD
>& xEquationTarget
,
1302 bool bMaySkipPoints
)
1306 const rtl::Reference
< DataSeries
>& xContainer( rVDataSeries
.getModel() );
1307 if(!xContainer
.is())
1313 const std::vector
< rtl::Reference
< ::chart::RegressionCurveModel
> > & aCurveList
= xContainer
->getRegressionCurves2();
1315 for(std::size_t nN
=0; nN
<aCurveList
.size(); nN
++)
1317 const auto & rCurve
= aCurveList
[nN
];
1318 uno::Reference
< XRegressionCurveCalculator
> xCalculator( rCurve
->getCalculator() );
1319 if( !xCalculator
.is())
1322 bool bAverageLine
= RegressionCurveHelper::isMeanValueLine( rCurve
);
1324 sal_Int32 aDegree
= 2;
1325 sal_Int32 aPeriod
= 2;
1326 sal_Int32 aMovingAverageType
= css::chart2::MovingAverageType::Prior
;
1327 double aExtrapolateForward
= 0.0;
1328 double aExtrapolateBackward
= 0.0;
1329 bool bForceIntercept
= false;
1330 double aInterceptValue
= 0.0;
1332 if ( !bAverageLine
)
1334 rCurve
->getPropertyValue( u
"PolynomialDegree"_ustr
) >>= aDegree
;
1335 rCurve
->getPropertyValue( u
"MovingAveragePeriod"_ustr
) >>= aPeriod
;
1336 rCurve
->getPropertyValue( u
"MovingAverageType"_ustr
) >>= aMovingAverageType
;
1337 rCurve
->getPropertyValue( u
"ExtrapolateForward"_ustr
) >>= aExtrapolateForward
;
1338 rCurve
->getPropertyValue( u
"ExtrapolateBackward"_ustr
) >>= aExtrapolateBackward
;
1339 rCurve
->getPropertyValue( u
"ForceIntercept"_ustr
) >>= bForceIntercept
;
1340 if (bForceIntercept
)
1341 rCurve
->getPropertyValue( u
"InterceptValue"_ustr
) >>= aInterceptValue
;
1344 double fChartMinX
= m_pPosHelper
->getLogicMinX();
1345 double fChartMaxX
= m_pPosHelper
->getLogicMaxX();
1347 double fMinX
= fChartMinX
;
1348 double fMaxX
= fChartMaxX
;
1350 double fPointScale
= 1.0;
1354 rVDataSeries
.getMinMaxXValue(fMinX
, fMaxX
);
1355 fMaxX
+= aExtrapolateForward
;
1356 fMinX
-= aExtrapolateBackward
;
1358 fPointScale
= (fMaxX
- fMinX
) / (fChartMaxX
- fChartMinX
);
1359 // sanitize the value, tdf#119922
1360 fPointScale
= std::min(fPointScale
, 1000.0);
1363 xCalculator
->setRegressionProperties(aDegree
, bForceIntercept
, aInterceptValue
, aPeriod
,
1364 aMovingAverageType
);
1365 xCalculator
->recalculateRegression(rVDataSeries
.getAllX(), rVDataSeries
.getAllY());
1366 sal_Int32 nPointCount
= 100 * fPointScale
;
1368 if ( nPointCount
< 2 )
1371 std::vector
< ExplicitScaleData
> aScales( m_pPosHelper
->getScales());
1372 uno::Reference
< chart2::XScaling
> xScalingX
;
1373 uno::Reference
< chart2::XScaling
> xScalingY
;
1374 if( aScales
.size() >= 2 )
1376 xScalingX
.set( aScales
[0].Scaling
);
1377 xScalingY
.set( aScales
[1].Scaling
);
1380 const uno::Sequence
< geometry::RealPoint2D
> aCalculatedPoints(
1381 xCalculator
->getCurveValues(
1382 fMinX
, fMaxX
, nPointCount
,
1383 xScalingX
, xScalingY
, bMaySkipPoints
));
1385 nPointCount
= aCalculatedPoints
.getLength();
1387 drawing::PolyPolygonShape3D aRegressionPoly
;
1388 aRegressionPoly
.SequenceX
.realloc(1);
1389 aRegressionPoly
.SequenceY
.realloc(1);
1390 aRegressionPoly
.SequenceZ
.realloc(1);
1391 auto pSequenceX
= aRegressionPoly
.SequenceX
.getArray();
1392 auto pSequenceY
= aRegressionPoly
.SequenceY
.getArray();
1393 auto pSequenceZ
= aRegressionPoly
.SequenceZ
.getArray();
1394 pSequenceX
[0].realloc(nPointCount
);
1395 pSequenceY
[0].realloc(nPointCount
);
1396 auto pSequenceX0
= pSequenceX
[0].getArray();
1397 auto pSequenceY0
= pSequenceY
[0].getArray();
1399 sal_Int32 nRealPointCount
= 0;
1401 for(geometry::RealPoint2D
const & p
: aCalculatedPoints
)
1403 double fLogicX
= p
.X
;
1404 double fLogicY
= p
.Y
;
1405 double fLogicZ
= 0.0; //dummy
1407 // fdo#51656: don't scale mean value lines
1409 m_pPosHelper
->doLogicScaling( &fLogicX
, &fLogicY
, &fLogicZ
);
1411 if(!std::isnan(fLogicX
) && !std::isinf(fLogicX
) &&
1412 !std::isnan(fLogicY
) && !std::isinf(fLogicY
) &&
1413 !std::isnan(fLogicZ
) && !std::isinf(fLogicZ
) )
1415 pSequenceX0
[nRealPointCount
] = fLogicX
;
1416 pSequenceY0
[nRealPointCount
] = fLogicY
;
1420 pSequenceX
[0].realloc(nRealPointCount
);
1421 pSequenceY
[0].realloc(nRealPointCount
);
1422 pSequenceZ
[0].realloc(nRealPointCount
);
1424 drawing::PolyPolygonShape3D aClippedPoly
;
1425 Clipping::clipPolygonAtRectangle( aRegressionPoly
, m_pPosHelper
->getScaledLogicClipDoubleRect(), aClippedPoly
);
1426 aRegressionPoly
= std::move(aClippedPoly
);
1427 m_pPosHelper
->transformScaledLogicToScene( aRegressionPoly
);
1429 awt::Point aDefaultPos
;
1430 if( aRegressionPoly
.SequenceX
.hasElements() && aRegressionPoly
.SequenceX
[0].hasElements() )
1432 VLineProperties aVLineProperties
;
1433 aVLineProperties
.initFromPropertySet( rCurve
);
1435 //create an extra group shape for each curve for selection handling
1436 rtl::Reference
<SvxShapeGroupAnyD
> xRegressionGroupShapes
=
1437 createGroupShape( xTarget
, rVDataSeries
.getDataCurveCID( nN
, bAverageLine
) );
1438 rtl::Reference
<SvxShapePolyPolygon
> xShape
= ShapeFactory::createLine2D(
1439 xRegressionGroupShapes
, PolyToPointSequence( aRegressionPoly
), &aVLineProperties
);
1440 ShapeFactory::setShapeName( xShape
, u
"MarkHandles"_ustr
);
1441 aDefaultPos
= xShape
->getPosition();
1444 // curve equation and correlation coefficient
1445 uno::Reference
< beans::XPropertySet
> xEquationProperties( rCurve
->getEquationProperties());
1446 if( xEquationProperties
.is())
1448 createRegressionCurveEquationShapes(
1449 rVDataSeries
.getDataCurveEquationCID( nN
),
1450 xEquationProperties
, xEquationTarget
, xCalculator
,
1456 static sal_Int32
lcl_getOUStringMaxLineLength ( OUStringBuffer
const & aString
)
1458 const sal_Int32 nStringLength
= aString
.getLength();
1459 sal_Int32 nMaxLineLength
= 0;
1461 for ( sal_Int32 i
=0; i
<nStringLength
; i
++ )
1463 sal_Int32 indexSep
= aString
.indexOf( "\n", i
);
1465 indexSep
= nStringLength
;
1466 sal_Int32 nLineLength
= indexSep
- i
;
1467 if ( nLineLength
> nMaxLineLength
)
1468 nMaxLineLength
= nLineLength
;
1472 return nMaxLineLength
;
1475 void VSeriesPlotter::createRegressionCurveEquationShapes(
1476 const OUString
& rEquationCID
,
1477 const uno::Reference
< beans::XPropertySet
> & xEquationProperties
,
1478 const rtl::Reference
<SvxShapeGroupAnyD
>& xEquationTarget
,
1479 const uno::Reference
< chart2::XRegressionCurveCalculator
> & xRegressionCurveCalculator
,
1480 awt::Point aDefaultPos
)
1482 OSL_ASSERT( xEquationProperties
.is());
1483 if( !xEquationProperties
.is())
1486 bool bShowEquation
= false;
1487 bool bShowCorrCoeff
= false;
1488 if(!(( xEquationProperties
->getPropertyValue( u
"ShowEquation"_ustr
) >>= bShowEquation
) &&
1489 ( xEquationProperties
->getPropertyValue( u
"ShowCorrelationCoefficient"_ustr
) >>= bShowCorrCoeff
)))
1492 if( ! (bShowEquation
|| bShowCorrCoeff
))
1495 OUStringBuffer aFormula
;
1496 sal_Int32 nNumberFormatKey
= 0;
1497 sal_Int32 nFormulaWidth
= 0;
1498 xEquationProperties
->getPropertyValue(CHART_UNONAME_NUMFMT
) >>= nNumberFormatKey
;
1499 bool bResizeEquation
= true;
1500 sal_Int32 nMaxIteration
= 2;
1501 if ( bShowEquation
)
1503 OUString aXName
, aYName
;
1504 if ( !(xEquationProperties
->getPropertyValue( u
"XName"_ustr
) >>= aXName
) )
1506 if ( !(xEquationProperties
->getPropertyValue( u
"YName"_ustr
) >>= aYName
) )
1507 aYName
= u
"f(x)"_ustr
;
1508 xRegressionCurveCalculator
->setXYNames( aXName
, aYName
);
1511 for ( sal_Int32 nCountIteration
= 0; bResizeEquation
&& nCountIteration
< nMaxIteration
; nCountIteration
++ )
1513 bResizeEquation
= false;
1516 if (m_apNumberFormatterWrapper
)
1517 { // iteration 0: default representation (no wrap)
1518 // iteration 1: expected width (nFormulaWidth) is calculated
1519 aFormula
= xRegressionCurveCalculator
->getFormattedRepresentation(
1520 m_apNumberFormatterWrapper
->getNumberFormatsSupplier(),
1521 nNumberFormatKey
, nFormulaWidth
);
1522 nFormulaWidth
= lcl_getOUStringMaxLineLength( aFormula
);
1526 aFormula
= xRegressionCurveCalculator
->getRepresentation();
1529 if( bShowCorrCoeff
)
1531 aFormula
.append( "\n" );
1534 if( bShowCorrCoeff
)
1536 aFormula
.append( "R" + OUStringChar( aSuperscriptFigures
[2] ) + " = " );
1537 double fR( xRegressionCurveCalculator
->getCorrelationCoefficient());
1538 if (m_apNumberFormatterWrapper
)
1543 m_apNumberFormatterWrapper
->getFormattedString(
1544 nNumberFormatKey
, fR
*fR
, nLabelCol
, bColChanged
));
1545 //@todo: change color of label if bColChanged is true
1549 const LocaleDataWrapper
& rLocaleDataWrapper
= Application::GetSettings().GetLocaleDataWrapper();
1550 const OUString
& aNumDecimalSep
= rLocaleDataWrapper
.getNumDecimalSep();
1551 sal_Unicode aDecimalSep
= aNumDecimalSep
[0];
1552 aFormula
.append( ::rtl::math::doubleToUString(
1553 fR
*fR
, rtl_math_StringFormat_G
, 4, aDecimalSep
, true ));
1557 awt::Point aScreenPosition2D
;
1558 chart2::RelativePosition aRelativePosition
;
1559 if( xEquationProperties
->getPropertyValue( u
"RelativePosition"_ustr
) >>= aRelativePosition
)
1561 //@todo decide whether x is primary or secondary
1562 double fX
= aRelativePosition
.Primary
*m_aPageReferenceSize
.Width
;
1563 double fY
= aRelativePosition
.Secondary
*m_aPageReferenceSize
.Height
;
1564 aScreenPosition2D
.X
= static_cast< sal_Int32
>( ::rtl::math::round( fX
));
1565 aScreenPosition2D
.Y
= static_cast< sal_Int32
>( ::rtl::math::round( fY
));
1568 aScreenPosition2D
= aDefaultPos
;
1570 if( !aFormula
.isEmpty())
1572 // set fill and line properties on creation
1573 tNameSequence aNames
;
1574 tAnySequence aValues
;
1575 PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties
, aNames
, aValues
);
1577 rtl::Reference
<SvxShapeText
> xTextShape
= ShapeFactory::createText(
1578 xEquationTarget
, aFormula
.makeStringAndClear(),
1579 aNames
, aValues
, ShapeFactory::makeTransformation( aScreenPosition2D
));
1581 ShapeFactory::setShapeName( xTextShape
, rEquationCID
);
1582 awt::Size
aSize( xTextShape
->getSize() );
1583 awt::Point
aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
1584 aScreenPosition2D
, aSize
, aRelativePosition
.Anchor
) );
1585 //ensure that the equation is fully placed within the page (if possible)
1586 if( (aPos
.X
+ aSize
.Width
) > m_aPageReferenceSize
.Width
)
1587 aPos
.X
= m_aPageReferenceSize
.Width
- aSize
.Width
;
1591 if ( nFormulaWidth
> 0 )
1593 bResizeEquation
= true;
1594 if ( nCountIteration
< nMaxIteration
-1 )
1595 xEquationTarget
->remove( xTextShape
); // remove equation
1596 nFormulaWidth
*= m_aPageReferenceSize
.Width
/ static_cast< double >(aSize
.Width
);
1597 nFormulaWidth
-= nCountIteration
;
1598 if ( nFormulaWidth
< 0 )
1602 if( (aPos
.Y
+ aSize
.Height
) > m_aPageReferenceSize
.Height
)
1603 aPos
.Y
= m_aPageReferenceSize
.Height
- aSize
.Height
;
1606 if ( !bResizeEquation
|| nCountIteration
== nMaxIteration
-1 )
1607 xTextShape
->setPosition(aPos
); // if equation was not removed
1612 void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution
, const Date
& rNullDate
)
1614 m_nTimeResolution
= TimeResolution
;
1615 m_aNullDate
= rNullDate
;
1618 // MinimumAndMaximumSupplier
1619 tools::Long
VSeriesPlotter::calculateTimeResolutionOnXAxis()
1621 tools::Long nRet
= css::chart::TimeUnit::YEAR
;
1622 if (!m_pExplicitCategoriesProvider
)
1625 const std::vector
<double>& rDateCategories
= m_pExplicitCategoriesProvider
->getDateCategories();
1626 if (rDateCategories
.empty())
1629 std::vector
<double>::const_iterator aIt
= rDateCategories
.begin(), aEnd
= rDateCategories
.end();
1631 aIt
= std::find_if(aIt
, aEnd
, [](const double& rDateCategory
) { return !std::isnan(rDateCategory
); });
1635 Date
aNullDate(30,12,1899);
1636 if (m_apNumberFormatterWrapper
)
1637 aNullDate
= m_apNumberFormatterWrapper
->getNullDate();
1639 Date
aPrevious(aNullDate
); aPrevious
.AddDays(rtl::math::approxFloor(*aIt
));
1641 for(;aIt
!=aEnd
;++aIt
)
1643 if (std::isnan(*aIt
))
1646 Date
aCurrent(aNullDate
); aCurrent
.AddDays(rtl::math::approxFloor(*aIt
));
1647 if( nRet
== css::chart::TimeUnit::YEAR
)
1649 if( DateHelper::IsInSameYear( aPrevious
, aCurrent
) )
1650 nRet
= css::chart::TimeUnit::MONTH
;
1652 if( nRet
== css::chart::TimeUnit::MONTH
)
1654 if( DateHelper::IsInSameMonth( aPrevious
, aCurrent
) )
1655 nRet
= css::chart::TimeUnit::DAY
;
1657 if( nRet
== css::chart::TimeUnit::DAY
)
1664 double VSeriesPlotter::getMinimumX()
1666 double fMinimum
, fMaximum
;
1667 getMinimumAndMaximumX( fMinimum
, fMaximum
);
1670 double VSeriesPlotter::getMaximumX()
1672 double fMinimum
, fMaximum
;
1673 getMinimumAndMaximumX( fMinimum
, fMaximum
);
1677 double VSeriesPlotter::getMinimumYInRange( double fMinimumX
, double fMaximumX
, sal_Int32 nAxisIndex
)
1679 if( !m_bCategoryXAxis
|| ( m_pExplicitCategoriesProvider
&& m_pExplicitCategoriesProvider
->isDateAxis() ) )
1681 double fMinY
, fMaxY
;
1682 getMinimumAndMaximumYInContinuousXRange( fMinY
, fMaxY
, fMinimumX
, fMaximumX
, nAxisIndex
);
1686 double fMinimum
= std::numeric_limits
<double>::infinity();
1687 double fMaximum
= -std::numeric_limits
<double>::infinity();
1688 for(std::vector
<VDataSeriesGroup
> & rXSlots
: m_aZSlots
)
1690 for(VDataSeriesGroup
& rXSlot
: rXSlots
)
1692 double fLocalMinimum
, fLocalMaximum
;
1693 rXSlot
.calculateYMinAndMaxForCategoryRange(
1694 static_cast<sal_Int32
>(fMinimumX
-1.0) //first category (index 0) matches with real number 1.0
1695 , static_cast<sal_Int32
>(fMaximumX
-1.0) //first category (index 0) matches with real number 1.0
1696 , isSeparateStackingForDifferentSigns( 1 )
1697 , fLocalMinimum
, fLocalMaximum
, nAxisIndex
);
1698 if(fMaximum
<fLocalMaximum
)
1699 fMaximum
=fLocalMaximum
;
1700 if(fMinimum
>fLocalMinimum
)
1701 fMinimum
=fLocalMinimum
;
1704 if(std::isinf(fMinimum
))
1705 return std::numeric_limits
<double>::quiet_NaN();
1709 double VSeriesPlotter::getMaximumYInRange( double fMinimumX
, double fMaximumX
, sal_Int32 nAxisIndex
)
1711 if( !m_bCategoryXAxis
|| ( m_pExplicitCategoriesProvider
&& m_pExplicitCategoriesProvider
->isDateAxis() ) )
1713 double fMinY
, fMaxY
;
1714 getMinimumAndMaximumYInContinuousXRange( fMinY
, fMaxY
, fMinimumX
, fMaximumX
, nAxisIndex
);
1718 double fMinimum
= std::numeric_limits
<double>::infinity();
1719 double fMaximum
= -std::numeric_limits
<double>::infinity();
1720 for( std::vector
< VDataSeriesGroup
> & rXSlots
: m_aZSlots
)
1722 for(VDataSeriesGroup
& rXSlot
: rXSlots
)
1724 double fLocalMinimum
, fLocalMaximum
;
1725 rXSlot
.calculateYMinAndMaxForCategoryRange(
1726 static_cast<sal_Int32
>(fMinimumX
-1.0) //first category (index 0) matches with real number 1.0
1727 , static_cast<sal_Int32
>(fMaximumX
-1.0) //first category (index 0) matches with real number 1.0
1728 , isSeparateStackingForDifferentSigns( 1 )
1729 , fLocalMinimum
, fLocalMaximum
, nAxisIndex
);
1730 if(fMaximum
<fLocalMaximum
)
1731 fMaximum
=fLocalMaximum
;
1732 if(fMinimum
>fLocalMinimum
)
1733 fMinimum
=fLocalMinimum
;
1736 if(std::isinf(fMaximum
))
1737 return std::numeric_limits
<double>::quiet_NaN();
1741 double VSeriesPlotter::getMinimumZ()
1743 //this is the default for all charts without a meaningful z axis
1746 double VSeriesPlotter::getMaximumZ()
1748 if( m_nDimension
!=3 || m_aZSlots
.empty() )
1749 return getMinimumZ()+1;
1750 return m_aZSlots
.size();
1755 bool lcl_isValueAxis( sal_Int32 nDimensionIndex
, bool bCategoryXAxis
)
1757 // default implementation: true for Y axes, and for value X axis
1758 if( nDimensionIndex
== 0 )
1759 return !bCategoryXAxis
;
1760 return nDimensionIndex
== 1;
1764 bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex
)
1766 return lcl_isValueAxis( nDimensionIndex
, m_bCategoryXAxis
);
1769 bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex
)
1771 // do not expand axes in 3D charts
1772 return (m_nDimension
< 3) && lcl_isValueAxis( nDimensionIndex
, m_bCategoryXAxis
);
1775 bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex
)
1777 // default implementation: only for Y axis
1778 return nDimensionIndex
== 1;
1781 bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex
)
1783 // default implementation: only for Y axis
1784 return nDimensionIndex
== 1;
1787 bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex
)
1789 // default implementation: only for Y axis
1790 return nDimensionIndex
== 1;
1793 void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum
, double& rfMaximum
) const
1795 rfMinimum
= std::numeric_limits
<double>::infinity();
1796 rfMaximum
= -std::numeric_limits
<double>::infinity();
1798 for (auto const& ZSlot
: m_aZSlots
)
1800 for (auto const& XSlot
: ZSlot
)
1802 double fLocalMinimum
, fLocalMaximum
;
1803 XSlot
.getMinimumAndMaximumX( fLocalMinimum
, fLocalMaximum
);
1804 if( !std::isnan(fLocalMinimum
) && fLocalMinimum
< rfMinimum
)
1805 rfMinimum
= fLocalMinimum
;
1806 if( !std::isnan(fLocalMaximum
) && fLocalMaximum
> rfMaximum
)
1807 rfMaximum
= fLocalMaximum
;
1810 if(std::isinf(rfMinimum
))
1811 rfMinimum
= std::numeric_limits
<double>::quiet_NaN();
1812 if(std::isinf(rfMaximum
))
1813 rfMaximum
= std::numeric_limits
<double>::quiet_NaN();
1816 void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY
, double& rfMaxY
, double fMinX
, double fMaxX
, sal_Int32 nAxisIndex
) const
1818 rfMinY
= std::numeric_limits
<double>::infinity();
1819 rfMaxY
= -std::numeric_limits
<double>::infinity();
1821 for (auto const& ZSlot
: m_aZSlots
)
1823 for (auto const& XSlot
: ZSlot
)
1825 double fLocalMinimum
, fLocalMaximum
;
1826 XSlot
.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum
, fLocalMaximum
, fMinX
, fMaxX
, nAxisIndex
);
1827 if( !std::isnan(fLocalMinimum
) && fLocalMinimum
< rfMinY
)
1828 rfMinY
= fLocalMinimum
;
1829 if( !std::isnan(fLocalMaximum
) && fLocalMaximum
> rfMaxY
)
1830 rfMaxY
= fLocalMaximum
;
1833 if(std::isinf(rfMinY
))
1834 rfMinY
= std::numeric_limits
<double>::quiet_NaN();
1835 if(std::isinf(rfMaxY
))
1836 rfMaxY
= std::numeric_limits
<double>::quiet_NaN();
1839 sal_Int32
VSeriesPlotter::getPointCount() const
1843 for (auto const& ZSlot
: m_aZSlots
)
1845 for (auto const& XSlot
: ZSlot
)
1847 sal_Int32 nPointCount
= XSlot
.getPointCount();
1848 if( nPointCount
>nRet
)
1855 void VSeriesPlotter::setNumberFormatsSupplier(
1856 const uno::Reference
< util::XNumberFormatsSupplier
> & xNumFmtSupplier
)
1858 m_apNumberFormatterWrapper
.reset( new NumberFormatterWrapper( xNumFmtSupplier
));
1861 void VSeriesPlotter::setColorScheme( const uno::Reference
< XColorScheme
>& xColorScheme
)
1863 m_xColorScheme
= xColorScheme
;
1866 void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider
* pExplicitCategoriesProvider
)
1868 m_pExplicitCategoriesProvider
= pExplicitCategoriesProvider
;
1871 sal_Int32
VDataSeriesGroup::getPointCount() const
1873 if(!m_bMaxPointCountDirty
)
1874 return m_nMaxPointCount
;
1878 for (std::unique_ptr
<VDataSeries
> const & pSeries
: m_aSeriesVector
)
1880 sal_Int32 nPointCount
= pSeries
->getTotalPointCount();
1881 if( nPointCount
>nRet
)
1884 m_nMaxPointCount
=nRet
;
1885 m_aListOfCachedYValues
.clear();
1886 m_aListOfCachedYValues
.resize(m_nMaxPointCount
);
1887 m_bMaxPointCountDirty
=false;
1891 sal_Int32
VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1895 if (!m_aSeriesVector
.empty())
1896 nRet
= m_aSeriesVector
[0]->getAttachedAxisIndex();
1901 void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum
, double& rfMaximum
) const
1904 rfMinimum
= std::numeric_limits
<double>::infinity();
1905 rfMaximum
= -std::numeric_limits
<double>::infinity();
1907 for (std::unique_ptr
<VDataSeries
> const & pSeries
: m_aSeriesVector
)
1909 sal_Int32 nPointCount
= pSeries
->getTotalPointCount();
1910 for(sal_Int32 nN
=0;nN
<nPointCount
;nN
++)
1912 double fX
= pSeries
->getXValue( nN
);
1913 if( std::isnan(fX
) )
1921 if(std::isinf(rfMinimum
))
1922 rfMinimum
= std::numeric_limits
<double>::quiet_NaN();
1923 if(std::isinf(rfMaximum
))
1924 rfMaximum
= std::numeric_limits
<double>::quiet_NaN();
1930 * Keep track of minimum and maximum Y values for one or more data series.
1931 * When multiple data series exist, that indicates that the data series are
1934 * <p>For each X value, we calculate separate Y value ranges for each data
1935 * series in the first pass. In the second pass, we calculate the minimum Y
1936 * value by taking the absolute minimum value of all data series, whereas
1937 * the maximum Y value is the sum of all the series maximum Y values.</p>
1939 * <p>Once that's done for all X values, the final min / max Y values get
1940 * calculated by taking the absolute min / max Y values across all the X
1943 class PerXMinMaxCalculator
1945 typedef std::pair
<double, double> MinMaxType
;
1946 typedef std::map
<size_t, MinMaxType
> SeriesMinMaxType
;
1947 typedef std::map
<double, SeriesMinMaxType
> GroupMinMaxType
;
1948 typedef std::unordered_map
<double, MinMaxType
> TotalStoreType
;
1949 GroupMinMaxType m_SeriesGroup
;
1953 PerXMinMaxCalculator() : mnCurSeries(0) {}
1955 void nextSeries() { ++mnCurSeries
; }
1957 void setValue(double fX
, double fY
)
1959 SeriesMinMaxType
* pStore
= getByXValue(fX
); // get storage for given X value.
1961 // This shouldn't happen!
1964 SeriesMinMaxType::iterator it
= pStore
->lower_bound(mnCurSeries
);
1965 if (it
!= pStore
->end() && !pStore
->key_comp()(mnCurSeries
, it
->first
))
1967 MinMaxType
& r
= it
->second
;
1968 // A min-max pair already exists for this series. Update it.
1976 // No existing pair. Insert a new one.
1978 it
, SeriesMinMaxType::value_type(
1979 mnCurSeries
, MinMaxType(fY
,fY
)));
1983 void getTotalRange(double& rfMin
, double& rfMax
) const
1985 TotalStoreType aStore
;
1986 getTotalStore(aStore
);
1990 rfMin
= std::numeric_limits
<double>::quiet_NaN();
1991 rfMax
= std::numeric_limits
<double>::quiet_NaN();
1995 TotalStoreType::const_iterator it
= aStore
.begin(), itEnd
= aStore
.end();
1996 rfMin
= it
->second
.first
;
1997 rfMax
= it
->second
.second
;
1998 for (++it
; it
!= itEnd
; ++it
)
2000 if (rfMin
> it
->second
.first
)
2001 rfMin
= it
->second
.first
;
2002 if (rfMax
< it
->second
.second
)
2003 rfMax
= it
->second
.second
;
2009 * Parse all data and reduce them into a set of global Y value ranges per
2012 void getTotalStore(TotalStoreType
& rStore
) const
2014 TotalStoreType aStore
;
2015 for (auto const& it
: m_SeriesGroup
)
2017 double fX
= it
.first
;
2019 const SeriesMinMaxType
& rSeries
= it
.second
;
2020 for (auto const& series
: rSeries
)
2022 double fYMin
= series
.second
.first
, fYMax
= series
.second
.second
;
2023 TotalStoreType::iterator itr
= aStore
.find(fX
);
2024 if (itr
== aStore
.end())
2025 // New min-max pair for give X value.
2026 aStore
.emplace(fX
, std::pair
<double,double>(fYMin
,fYMax
));
2029 MinMaxType
& r
= itr
->second
;
2030 if (fYMin
< r
.first
)
2031 r
.first
= fYMin
; // min y-value
2033 r
.second
+= fYMax
; // accumulative max y-value.
2037 rStore
.swap(aStore
);
2040 SeriesMinMaxType
* getByXValue(double fX
)
2042 GroupMinMaxType::iterator it
= m_SeriesGroup
.find(fX
);
2043 if (it
== m_SeriesGroup
.end())
2045 std::pair
<GroupMinMaxType::iterator
,bool> r
=
2046 m_SeriesGroup
.insert(std::make_pair(fX
, SeriesMinMaxType
{}));
2049 // insertion failed.
2061 void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
2062 double& rfMinY
, double& rfMaxY
, double fMinX
, double fMaxX
, sal_Int32 nAxisIndex
) const
2064 rfMinY
= std::numeric_limits
<double>::quiet_NaN();
2065 rfMaxY
= std::numeric_limits
<double>::quiet_NaN();
2067 if (m_aSeriesVector
.empty())
2068 // No data series. Bail out.
2071 PerXMinMaxCalculator aRangeCalc
;
2072 for (const std::unique_ptr
<VDataSeries
> & pSeries
: m_aSeriesVector
)
2077 for (sal_Int32 i
= 0, n
= pSeries
->getTotalPointCount(); i
< n
; ++i
)
2079 if (nAxisIndex
!= pSeries
->getAttachedAxisIndex())
2082 double fX
= pSeries
->getXValue(i
);
2086 if (fX
< fMinX
|| fX
> fMaxX
)
2087 // Outside specified X range. Skip it.
2090 double fY
= pSeries
->getYValue(i
);
2094 aRangeCalc
.setValue(fX
, fY
);
2096 aRangeCalc
.nextSeries();
2099 aRangeCalc
.getTotalRange(rfMinY
, rfMaxY
);
2102 void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
2103 , bool bSeparateStackingForDifferentSigns
2104 , double& rfMinimumY
, double& rfMaximumY
, sal_Int32 nAxisIndex
) const
2106 assert(nCategoryIndex
>= 0);
2107 assert(nCategoryIndex
< getPointCount());
2109 rfMinimumY
= std::numeric_limits
<double>::infinity();
2110 rfMaximumY
= -std::numeric_limits
<double>::infinity();
2112 if(m_aSeriesVector
.empty())
2115 CachedYValues aCachedYValues
= m_aListOfCachedYValues
[nCategoryIndex
][nAxisIndex
];
2116 if( !aCachedYValues
.m_bValuesDirty
)
2118 //return cached values
2119 rfMinimumY
= aCachedYValues
.m_fMinimumY
;
2120 rfMaximumY
= aCachedYValues
.m_fMaximumY
;
2124 double fTotalSum
= std::numeric_limits
<double>::quiet_NaN();
2125 double fPositiveSum
= std::numeric_limits
<double>::quiet_NaN();
2126 double fNegativeSum
= std::numeric_limits
<double>::quiet_NaN();
2127 double fFirstPositiveY
= std::numeric_limits
<double>::quiet_NaN();
2128 double fFirstNegativeY
= std::numeric_limits
<double>::quiet_NaN();
2130 if( bSeparateStackingForDifferentSigns
)
2132 for (const std::unique_ptr
<VDataSeries
> & pSeries
: m_aSeriesVector
)
2134 if( nAxisIndex
!= pSeries
->getAttachedAxisIndex() )
2137 double fValueMinY
= pSeries
->getMinimumofAllDifferentYValues( nCategoryIndex
);
2138 double fValueMaxY
= pSeries
->getMaximumofAllDifferentYValues( nCategoryIndex
);
2140 if( fValueMaxY
>= 0 )
2142 if( std::isnan( fPositiveSum
) )
2143 fPositiveSum
= fFirstPositiveY
= fValueMaxY
;
2145 fPositiveSum
+= fValueMaxY
;
2147 if( fValueMinY
< 0 )
2149 if(std::isnan( fNegativeSum
))
2150 fNegativeSum
= fFirstNegativeY
= fValueMinY
;
2152 fNegativeSum
+= fValueMinY
;
2155 rfMinimumY
= std::isnan( fNegativeSum
) ? fFirstPositiveY
: fNegativeSum
;
2156 rfMaximumY
= std::isnan( fPositiveSum
) ? fFirstNegativeY
: fPositiveSum
;
2160 for (const std::unique_ptr
<VDataSeries
> & pSeries
: m_aSeriesVector
)
2162 if( nAxisIndex
!= pSeries
->getAttachedAxisIndex() )
2165 double fValueMinY
= pSeries
->getMinimumofAllDifferentYValues( nCategoryIndex
);
2166 double fValueMaxY
= pSeries
->getMaximumofAllDifferentYValues( nCategoryIndex
);
2168 if( std::isnan( fTotalSum
) )
2170 rfMinimumY
= fValueMinY
;
2171 rfMaximumY
= fTotalSum
= fValueMaxY
;
2175 fTotalSum
+= fValueMaxY
;
2176 if( rfMinimumY
> fTotalSum
)
2177 rfMinimumY
= fTotalSum
;
2178 if( rfMaximumY
< fTotalSum
)
2179 rfMaximumY
= fTotalSum
;
2184 aCachedYValues
.m_fMinimumY
= rfMinimumY
;
2185 aCachedYValues
.m_fMaximumY
= rfMaximumY
;
2186 aCachedYValues
.m_bValuesDirty
= false;
2187 m_aListOfCachedYValues
[nCategoryIndex
][nAxisIndex
]=aCachedYValues
;
2190 void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
2191 sal_Int32 nStartCategoryIndex
, sal_Int32 nEndCategoryIndex
2192 , bool bSeparateStackingForDifferentSigns
2193 , double& rfMinimumY
, double& rfMaximumY
, sal_Int32 nAxisIndex
)
2195 //@todo maybe cache these values
2196 rfMinimumY
= std::numeric_limits
<double>::infinity();
2197 rfMaximumY
= -std::numeric_limits
<double>::infinity();
2199 //iterate through the given categories
2200 if(nStartCategoryIndex
<0)
2201 nStartCategoryIndex
=0;
2202 const sal_Int32 nPointCount
= getPointCount();//necessary to create m_aListOfCachedYValues
2203 if(nPointCount
<= 0)
2205 if (nEndCategoryIndex
>= nPointCount
)
2206 nEndCategoryIndex
= nPointCount
- 1;
2207 if(nEndCategoryIndex
<0)
2208 nEndCategoryIndex
=0;
2209 for( sal_Int32 nCatIndex
= nStartCategoryIndex
; nCatIndex
<= nEndCategoryIndex
; nCatIndex
++ )
2211 double fMinimumY
= std::numeric_limits
<double>::quiet_NaN();
2212 double fMaximumY
= std::numeric_limits
<double>::quiet_NaN();
2214 calculateYMinAndMaxForCategory( nCatIndex
2215 , bSeparateStackingForDifferentSigns
, fMinimumY
, fMaximumY
, nAxisIndex
);
2217 if(rfMinimumY
> fMinimumY
)
2218 rfMinimumY
= fMinimumY
;
2219 if(rfMaximumY
< fMaximumY
)
2220 rfMaximumY
= fMaximumY
;
2224 double VSeriesPlotter::getTransformedDepth() const
2226 double MinZ
= m_pMainPosHelper
->getLogicMinZ();
2227 double MaxZ
= m_pMainPosHelper
->getLogicMaxZ();
2228 m_pMainPosHelper
->doLogicScaling( nullptr, nullptr, &MinZ
);
2229 m_pMainPosHelper
->doLogicScaling( nullptr, nullptr, &MaxZ
);
2230 return FIXED_SIZE_FOR_3D_CHART_VOLUME
/(MaxZ
-MinZ
);
2233 void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData
& rScale
, sal_Int32 nAxisIndex
)
2238 m_aSecondaryValueScales
[nAxisIndex
]=rScale
;
2241 PlottingPositionHelper
& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex
) const
2243 PlottingPositionHelper
* pRet
= nullptr;
2246 tSecondaryPosHelperMap::const_iterator aPosIt
= m_aSecondaryPosHelperMap
.find( nAxisIndex
);
2247 if( aPosIt
!= m_aSecondaryPosHelperMap
.end() )
2249 pRet
= aPosIt
->second
.get();
2251 else if (m_pPosHelper
)
2253 tSecondaryValueScales::const_iterator aScaleIt
= m_aSecondaryValueScales
.find( nAxisIndex
);
2254 if( aScaleIt
!= m_aSecondaryValueScales
.end() )
2256 m_aSecondaryPosHelperMap
[nAxisIndex
] = m_pPosHelper
->createSecondaryPosHelper( aScaleIt
->second
);
2257 pRet
= m_aSecondaryPosHelperMap
[nAxisIndex
].get();
2262 pRet
= m_pMainPosHelper
;
2263 pRet
->setTimeResolution( m_nTimeResolution
, m_aNullDate
);
2267 void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size
& /*rPageSize*/ )
2271 VDataSeries
* VSeriesPlotter::getFirstSeries() const
2273 for (std::vector
<VDataSeriesGroup
> const & rGroup
: m_aZSlots
)
2275 if (!rGroup
.empty())
2277 if (!rGroup
[0].m_aSeriesVector
.empty())
2279 VDataSeries
* pSeries
= rGroup
[0].m_aSeriesVector
[0].get();
2288 OUString
VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex
) const
2290 if (m_pExplicitCategoriesProvider
)
2292 Sequence
< OUString
> aCategories(m_pExplicitCategoriesProvider
->getSimpleCategories());
2293 if (nPointIndex
>= 0 && nPointIndex
< aCategories
.getLength())
2295 return aCategories
[nPointIndex
];
2302 // The following it to support rendering order for combo charts. A chart type
2303 // with a lower rendering order is rendered before (i.e., behind) a chart with a
2304 // higher rendering order. The rendering orders are based on rough guesses about
2305 // how much one chart (type) will obscure another chart (type). The intent is to
2306 // minimize obscuring of data, by putting charts that generally cover more
2307 // pixels (e.g., area charts) behind ones that generally cover fewer (e.g., line
2311 ROrderPair(OUString n
, sal_Int32 r
) : chartName(std::move(n
)), renderOrder(r
) {}
2314 sal_Int32 renderOrder
;
2317 const ROrderPair pairList
[] = {
2318 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_AREA
, 0),
2319 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BAR
, 6), // bar & column are same
2320 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_COLUMN
, 6),
2321 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_HISTOGRAM
, 9),
2322 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_LINE
, 8),
2323 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER
, 5),
2324 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_PIE
, 1),
2325 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_NET
, 3),
2326 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET
, 2),
2327 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK
, 7),
2328 ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE
, 4)
2332 sal_Int32
VSeriesPlotter::getRenderOrder() const
2334 OUString aChartType
= m_xChartTypeModel
->getChartType();
2335 for (const auto& elem
: pairList
) {
2336 if (aChartType
.equalsIgnoreAsciiCase(elem
.chartName
)) {
2337 return elem
.renderOrder
;
2340 SAL_WARN("chart2", "Unsupported chart type in getRenderOrder()");
2344 std::vector
<VDataSeries
const*> VSeriesPlotter::getAllSeries() const
2346 std::vector
<VDataSeries
const*> aAllSeries
;
2347 for (std::vector
<VDataSeriesGroup
> const & rXSlot
: m_aZSlots
)
2349 for(VDataSeriesGroup
const & rGroup
: rXSlot
)
2351 for (std::unique_ptr
<VDataSeries
> const & p
: rGroup
.m_aSeriesVector
)
2352 aAllSeries
.push_back(p
.get());
2359 std::vector
<VDataSeries
*> VSeriesPlotter::getAllSeries()
2361 std::vector
<VDataSeries
*> aAllSeries
;
2362 for (std::vector
<VDataSeriesGroup
> const & rXSlot
: m_aZSlots
)
2364 for(VDataSeriesGroup
const & rGroup
: rXSlot
)
2366 for (std::unique_ptr
<VDataSeries
> const & p
: rGroup
.m_aSeriesVector
)
2367 aAllSeries
.push_back(p
.get());
2373 uno::Sequence
<OUString
> VSeriesPlotter::getSeriesNames() const
2375 std::vector
<OUString
> aRetVector
;
2378 if (m_xChartTypeModel
.is())
2379 aRole
= m_xChartTypeModel
->getRoleOfSequenceForSeriesLabel();
2381 for (auto const& rGroup
: m_aZSlots
)
2383 if (!rGroup
.empty())
2385 VDataSeriesGroup
const & rSeriesGroup(rGroup
[0]);
2386 if (!rSeriesGroup
.m_aSeriesVector
.empty())
2388 VDataSeries
const * pSeries
= rSeriesGroup
.m_aSeriesVector
[0].get();
2389 rtl::Reference
< DataSeries
> xSeries( pSeries
? pSeries
->getModel() : nullptr );
2392 OUString
aSeriesName( xSeries
->getLabelForRole( aRole
) );
2393 aRetVector
.push_back( aSeriesName
);
2398 return comphelper::containerToSequence( aRetVector
);
2401 uno::Sequence
<OUString
> VSeriesPlotter::getAllSeriesNames() const
2403 std::vector
<OUString
> aRetVector
;
2406 if (m_xChartTypeModel
.is())
2407 aRole
= m_xChartTypeModel
->getRoleOfSequenceForSeriesLabel();
2409 for (VDataSeries
const* pSeries
: getAllSeries())
2413 OUString
aSeriesName(pSeries
->getModel()->getLabelForRole(aRole
));
2414 aRetVector
.push_back(aSeriesName
);
2417 return comphelper::containerToSequence(aRetVector
);
2420 void VSeriesPlotter::setPageReferenceSize( const css::awt::Size
& rPageRefSize
)
2422 m_aPageReferenceSize
= rPageRefSize
;
2424 // set reference size also at all data series
2426 for (auto const & outer
: m_aZSlots
)
2427 for (VDataSeriesGroup
const & rGroup
: outer
)
2429 for (std::unique_ptr
<VDataSeries
> const & pSeries
: rGroup
.m_aSeriesVector
)
2431 pSeries
->setPageReferenceSize(m_aPageReferenceSize
);
2436 //better performance for big data
2437 void VSeriesPlotter::setCoordinateSystemResolution( const Sequence
< sal_Int32
>& rCoordinateSystemResolution
)
2439 m_aCoordinateSystemResolution
= rCoordinateSystemResolution
;
2442 bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
2444 return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel
);
2447 bool VSeriesPlotter::shouldSnapRectToUsedArea()
2449 return m_nDimension
!= 3;
2452 std::vector
< ViewLegendEntry
> VSeriesPlotter::createLegendEntries(
2453 const awt::Size
& rEntryKeyAspectRatio
2454 , LegendPosition eLegendPosition
2455 , const Reference
< beans::XPropertySet
>& xTextProperties
2456 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
2457 , const Reference
< uno::XComponentContext
>& xContext
2458 , ChartModel
& rModel
2461 std::vector
< ViewLegendEntry
> aResult
;
2465 rtl::Reference
< Diagram
> xDiagram
= rModel
.getFirstChartDiagram();
2466 rtl::Reference
< BaseCoordinateSystem
> xCooSys(xDiagram
->getBaseCoordinateSystems()[0]);
2467 bool bSwapXAndY
= false;
2471 xCooSys
->getPropertyValue( u
"SwapXAndYAxis"_ustr
) >>= bSwapXAndY
;
2473 catch( const uno::Exception
& )
2477 //iterate through all series
2478 bool bBreak
= false;
2479 bool bFirstSeries
= true;
2482 for (std::vector
<VDataSeriesGroup
> const & rGroupVector
: m_aZSlots
)
2484 for (VDataSeriesGroup
const & rGroup
: rGroupVector
)
2486 for (std::unique_ptr
<VDataSeries
> const & pSeries
: rGroup
.m_aSeriesVector
)
2491 // "ShowLegendEntry"
2492 if (!pSeries
->getModel()->getFastPropertyValue(PROP_DATASERIES_SHOW_LEGEND_ENTRY
).get
<sal_Bool
>())
2497 std::vector
<ViewLegendEntry
> aSeriesEntries(
2498 createLegendEntriesForSeries(
2499 rEntryKeyAspectRatio
, *pSeries
, xTextProperties
,
2500 xTarget
, xContext
));
2502 //add series entries to the result now
2504 // use only the first series if VaryColorsByPoint is set for the first series
2505 if (bFirstSeries
&& pSeries
->isVaryColorsByPoint())
2507 bFirstSeries
= false;
2509 // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
2510 // If the legend is top or bottom and we have a stacked bar-chart the normal order
2511 // is the correct one, unless the chart type is horizontal bar-chart.
2512 bool bReverse
= false;
2515 StackingDirection
eStackingDirection( pSeries
->getStackingDirection() );
2516 bReverse
= ( eStackingDirection
!= StackingDirection_Y_STACKING
);
2518 else if ( eLegendPosition
== LegendPosition_LINE_START
|| eLegendPosition
== LegendPosition_LINE_END
)
2520 StackingDirection
eStackingDirection( pSeries
->getStackingDirection() );
2521 bReverse
= ( eStackingDirection
== StackingDirection_Y_STACKING
);
2525 aResult
.insert( aResult
.begin(), aSeriesEntries
.begin(), aSeriesEntries
.end() );
2527 aResult
.insert( aResult
.end(), aSeriesEntries
.begin(), aSeriesEntries
.end() );
2538 std::vector
<ViewLegendSymbol
> VSeriesPlotter::createSymbols(const awt::Size
& rEntryKeyAspectRatio
2539 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
2540 , const Reference
<uno::XComponentContext
>& xContext
)
2542 std::vector
<ViewLegendSymbol
> aResult
;
2546 bool bBreak
= false;
2547 bool bFirstSeries
= true;
2549 for (std::vector
<VDataSeriesGroup
> const & rGroupVector
: m_aZSlots
)
2551 for (VDataSeriesGroup
const & rGroup
: rGroupVector
)
2553 for (std::unique_ptr
<VDataSeries
> const & pSeries
: rGroup
.m_aSeriesVector
)
2558 std::vector
<ViewLegendSymbol
> aSeriesSymbols
= createSymbolsForSeries(rEntryKeyAspectRatio
, *pSeries
, xTarget
, xContext
);
2560 //add series entries to the result now
2562 // use only the first series if VaryColorsByPoint is set for the first series
2563 if (bFirstSeries
&& pSeries
->isVaryColorsByPoint())
2566 bFirstSeries
= false;
2568 aResult
.insert(aResult
.end(), aSeriesSymbols
.begin(), aSeriesSymbols
.end());
2581 bool lcl_HasVisibleLine( const uno::Reference
< beans::XPropertySet
>& xProps
, bool& rbHasDashedLine
)
2583 bool bHasVisibleLine
= false;
2584 rbHasDashedLine
= false;
2585 drawing::LineStyle aLineStyle
= drawing::LineStyle_NONE
;
2586 if( xProps
.is() && ( xProps
->getPropertyValue( u
"LineStyle"_ustr
) >>= aLineStyle
) )
2588 if( aLineStyle
!= drawing::LineStyle_NONE
)
2589 bHasVisibleLine
= true;
2590 if( aLineStyle
== drawing::LineStyle_DASH
)
2591 rbHasDashedLine
= true;
2593 return bHasVisibleLine
;
2596 bool lcl_HasRegressionCurves( const VDataSeries
& rSeries
, bool& rbHasDashedLine
)
2598 bool bHasRegressionCurves
= false;
2599 const rtl::Reference
< DataSeries
>& xRegrCont( rSeries
.getModel() );
2600 for( const rtl::Reference
< RegressionCurveModel
> & rCurve
: xRegrCont
->getRegressionCurves2() )
2602 bHasRegressionCurves
= true;
2603 lcl_HasVisibleLine( rCurve
, rbHasDashedLine
);
2605 return bHasRegressionCurves
;
2608 LegendSymbolStyle
VSeriesPlotter::getLegendSymbolStyle()
2610 return LegendSymbolStyle::Box
;
2613 awt::Size
VSeriesPlotter::getPreferredLegendKeyAspectRatio()
2615 awt::Size
aRet(1000,1000);
2616 if( m_nDimension
==3 )
2619 bool bSeriesAllowsLines
= (getLegendSymbolStyle() == LegendSymbolStyle::Line
);
2620 bool bHasLines
= false;
2621 bool bHasDashedLines
= false;
2622 //iterate through all series
2623 for (VDataSeries
* pSeries
: getAllSeries())
2625 if( bSeriesAllowsLines
)
2627 bool bCurrentDashed
= false;
2628 if( lcl_HasVisibleLine( pSeries
->getPropertiesOfSeries(), bCurrentDashed
) )
2631 if( bCurrentDashed
)
2633 bHasDashedLines
= true;
2638 bool bRegressionHasDashedLines
=false;
2639 if( lcl_HasRegressionCurves( *pSeries
, bRegressionHasDashedLines
) )
2642 if( bRegressionHasDashedLines
)
2644 bHasDashedLines
= true;
2651 if( bHasDashedLines
)
2652 aRet
= awt::Size(1600,-1);
2654 aRet
= awt::Size(800,-1);
2659 uno::Any
VSeriesPlotter::getExplicitSymbol( const VDataSeries
& /*rSeries*/, sal_Int32
/*nPointIndex*/ )
2664 rtl::Reference
<SvxShapeGroup
> VSeriesPlotter::createLegendSymbolForSeries(
2665 const awt::Size
& rEntryKeyAspectRatio
2666 , const VDataSeries
& rSeries
2667 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
2670 LegendSymbolStyle eLegendSymbolStyle
= getLegendSymbolStyle();
2671 uno::Any
aExplicitSymbol( getExplicitSymbol( rSeries
, -1 ) );
2673 VLegendSymbolFactory::PropertyType ePropType
=
2674 VLegendSymbolFactory::PropertyType::FilledSeries
;
2676 // todo: maybe the property-style does not solely depend on the
2677 // legend-symbol type
2678 switch( eLegendSymbolStyle
)
2680 case LegendSymbolStyle::Line
:
2681 ePropType
= VLegendSymbolFactory::PropertyType::LineSeries
;
2686 rtl::Reference
<SvxShapeGroup
> xShape
= VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio
,
2687 xTarget
, eLegendSymbolStyle
,
2688 rSeries
.getPropertiesOfSeries(), ePropType
, aExplicitSymbol
);
2693 rtl::Reference
< SvxShapeGroup
> VSeriesPlotter::createLegendSymbolForPoint(
2694 const awt::Size
& rEntryKeyAspectRatio
2695 , const VDataSeries
& rSeries
2696 , sal_Int32 nPointIndex
2697 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
)
2700 LegendSymbolStyle eLegendSymbolStyle
= getLegendSymbolStyle();
2701 uno::Any
aExplicitSymbol( getExplicitSymbol(rSeries
,nPointIndex
) );
2703 VLegendSymbolFactory::PropertyType ePropType
=
2704 VLegendSymbolFactory::PropertyType::FilledSeries
;
2706 // todo: maybe the property-style does not solely depend on the
2707 // legend-symbol type
2708 switch( eLegendSymbolStyle
)
2710 case LegendSymbolStyle::Line
:
2711 ePropType
= VLegendSymbolFactory::PropertyType::LineSeries
;
2717 // the default properties for the data point are the data series properties.
2718 // If a data point has own attributes overwrite them
2719 const Reference
< beans::XPropertySet
>& xSeriesProps( rSeries
.getPropertiesOfSeries() );
2720 Reference
< beans::XPropertySet
> xPointSet( xSeriesProps
);
2721 if( rSeries
.isAttributedDataPoint( nPointIndex
) )
2722 xPointSet
.set( rSeries
.getPropertiesOfPoint( nPointIndex
));
2724 // if a data point has no own color use a color from the diagram's color scheme
2725 if( ! rSeries
.hasPointOwnColor( nPointIndex
))
2727 Reference
< util::XCloneable
> xCloneable( xPointSet
,uno::UNO_QUERY
);
2728 if( xCloneable
.is() && m_xColorScheme
.is() )
2730 xPointSet
.set( xCloneable
->createClone(), uno::UNO_QUERY
);
2731 Reference
< container::XChild
> xChild( xPointSet
, uno::UNO_QUERY
);
2733 xChild
->setParent( xSeriesProps
);
2735 OSL_ASSERT( xPointSet
.is());
2736 xPointSet
->setPropertyValue(
2737 u
"Color"_ustr
, uno::Any( m_xColorScheme
->getColorByIndex( nPointIndex
)));
2741 rtl::Reference
< SvxShapeGroup
> xShape
= VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio
,
2742 xTarget
, eLegendSymbolStyle
, xPointSet
, ePropType
, aExplicitSymbol
);
2747 std::vector
< ViewLegendEntry
> VSeriesPlotter::createLegendEntriesForSeries(
2748 const awt::Size
& rEntryKeyAspectRatio
2749 , const VDataSeries
& rSeries
2750 , const Reference
< beans::XPropertySet
>& xTextProperties
2751 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
2752 , const Reference
< uno::XComponentContext
>& xContext
2755 std::vector
< ViewLegendEntry
> aResult
;
2757 if( ! ( xTarget
.is() && xContext
.is() ) )
2762 ViewLegendEntry aEntry
;
2763 OUString aLabelText
;
2764 bool bVaryColorsByPoint
= rSeries
.isVaryColorsByPoint();
2765 bool bIsPie
= m_xChartTypeModel
->getChartType().equalsIgnoreAsciiCase(
2766 CHART2_SERVICE_NAME_CHARTTYPE_PIE
);
2771 bool bDonut
= false;
2773 if ((m_xChartTypeModel
->getFastPropertyValue(PROP_PIECHARTTYPE_USE_RINGS
) >>= bDonut
) && bDonut
)
2777 catch (const uno::Exception
&)
2781 if (bVaryColorsByPoint
|| bIsPie
)
2783 Sequence
< OUString
> aCategoryNames
;
2784 if( m_pExplicitCategoriesProvider
)
2785 aCategoryNames
= m_pExplicitCategoriesProvider
->getSimpleCategories();
2786 Sequence
<sal_Int32
> deletedLegendEntries
;
2789 // "DeletedLegendEntries"
2790 rSeries
.getModel()->getFastPropertyValue(PROP_DATASERIES_DELETED_LEGEND_ENTRIES
) >>= deletedLegendEntries
;
2792 catch (const uno::Exception
&)
2795 for( sal_Int32 nIdx
=0; nIdx
<aCategoryNames
.getLength(); ++nIdx
)
2797 bool deletedLegendEntry
= false;
2798 for (const auto& deletedLegendEntryIdx
: deletedLegendEntries
)
2800 if (nIdx
== deletedLegendEntryIdx
)
2802 deletedLegendEntry
= true;
2806 if (deletedLegendEntry
)
2810 rtl::Reference
< SvxShapeGroup
> xSymbolGroup(ShapeFactory::createGroup2D( xTarget
));
2812 // create the symbol
2813 rtl::Reference
< SvxShapeGroup
> xShape
= createLegendSymbolForPoint( rEntryKeyAspectRatio
,
2814 rSeries
, nIdx
, xSymbolGroup
);
2816 // set CID to symbol for selection
2819 aEntry
.xSymbol
= std::move(xSymbolGroup
);
2821 OUString
aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT
, nIdx
) );
2822 aChildParticle
= ObjectIdentifier::addChildParticle( aChildParticle
, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY
, 0 ) );
2823 OUString aCID
= ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries
.getSeriesParticle(), aChildParticle
);
2824 ShapeFactory::setShapeName( xShape
, aCID
);
2828 aLabelText
= aCategoryNames
[nIdx
];
2829 if( xShape
.is() || !aLabelText
.isEmpty() )
2831 aEntry
.xLabel
= FormattedStringHelper::createFormattedString( aLabelText
, xTextProperties
);
2832 aResult
.push_back(aEntry
);
2839 rtl::Reference
< SvxShapeGroup
> xSymbolGroup(ShapeFactory::createGroup2D( xTarget
));
2841 // create the symbol
2842 rtl::Reference
<SvxShapeGroup
> xShape
= createLegendSymbolForSeries(
2843 rEntryKeyAspectRatio
, rSeries
, xSymbolGroup
);
2845 // set CID to symbol for selection
2848 aEntry
.xSymbol
= std::move(xSymbolGroup
);
2850 OUString
aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY
, 0 ) );
2851 OUString aCID
= ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries
.getSeriesParticle(), aChildParticle
);
2852 ShapeFactory::setShapeName( xShape
, aCID
);
2856 aLabelText
= rSeries
.getModel()->getLabelForRole( m_xChartTypeModel
.is() ? m_xChartTypeModel
->getRoleOfSequenceForSeriesLabel() : u
"values-y"_ustr
);
2857 aEntry
.xLabel
= FormattedStringHelper::createFormattedString( aLabelText
, xTextProperties
);
2859 aResult
.push_back(aEntry
);
2862 // don't show legend entry of regression curve & friends if this type of chart
2863 // doesn't support statistics #i63016#, fdo#37197
2864 if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel
, m_nDimension
))
2867 const rtl::Reference
< DataSeries
>& xRegrCont
= rSeries
.getModel();
2870 const std::vector
< rtl::Reference
< RegressionCurveModel
> > & aCurves
= xRegrCont
->getRegressionCurves2();
2871 sal_Int32 i
= 0, nCount
= aCurves
.size();
2872 for( i
=0; i
<nCount
; ++i
)
2875 OUString
aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves
[i
] ) );
2876 replaceParamterInString( aResStr
, u
"%SERIESNAME", aLabelText
);
2877 aEntry
.xLabel
= FormattedStringHelper::createFormattedString( aResStr
, xTextProperties
);
2880 rtl::Reference
<SvxShapeGroup
> xSymbolGroup(ShapeFactory::createGroup2D( xTarget
));
2882 // create the symbol
2883 rtl::Reference
<SvxShapeGroup
> xShape
= VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio
,
2884 xSymbolGroup
, LegendSymbolStyle::Line
,
2886 VLegendSymbolFactory::PropertyType::Line
, uno::Any() );
2888 // set CID to symbol for selection
2891 aEntry
.xSymbol
= std::move(xSymbolGroup
);
2893 bool bAverageLine
= RegressionCurveHelper::isMeanValueLine( aCurves
[i
] );
2894 ObjectType eObjectType
= bAverageLine
? OBJECTTYPE_DATA_AVERAGE_LINE
: OBJECTTYPE_DATA_CURVE
;
2895 OUString
aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType
, i
) );
2896 aChildParticle
= ObjectIdentifier::addChildParticle( aChildParticle
, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY
, 0 ) );
2897 OUString aCID
= ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries
.getSeriesParticle(), aChildParticle
);
2898 ShapeFactory::setShapeName( xShape
, aCID
);
2901 aResult
.push_back(aEntry
);
2905 catch( const uno::Exception
& )
2907 DBG_UNHANDLED_EXCEPTION("chart2" );
2912 std::vector
<ViewLegendSymbol
> VSeriesPlotter::createSymbolsForSeries(
2913 const awt::Size
& rEntryKeyAspectRatio
2914 , const VDataSeries
& rSeries
2915 , const rtl::Reference
<SvxShapeGroupAnyD
>& xTarget
2916 , const Reference
<uno::XComponentContext
>& xContext
)
2918 std::vector
<ViewLegendSymbol
> aResult
;
2920 if (!(xTarget
.is() && xContext
.is()))
2925 ViewLegendSymbol aEntry
;
2927 rtl::Reference
<SvxShapeGroup
> xSymbolGroup(ShapeFactory::createGroup2D(xTarget
));
2929 // create the symbol
2930 rtl::Reference
<SvxShapeGroup
> xShape
= createLegendSymbolForSeries(rEntryKeyAspectRatio
, rSeries
, xSymbolGroup
);
2932 // set CID to symbol for selection
2935 aEntry
.xSymbol
= std::move(xSymbolGroup
);
2936 aResult
.push_back(aEntry
);
2939 catch (const uno::Exception
&)
2941 DBG_UNHANDLED_EXCEPTION("chart2" );
2946 VSeriesPlotter
* VSeriesPlotter::createSeriesPlotter(
2947 const rtl::Reference
<ChartType
>& xChartTypeModel
2948 , sal_Int32 nDimensionCount
2949 , bool bExcludingPositioning
)
2951 if (!xChartTypeModel
.is())
2954 OUString aChartType
= xChartTypeModel
->getChartType();
2956 VSeriesPlotter
* pRet
=nullptr;
2957 if( aChartType
.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN
) )
2958 pRet
= new BarChart(xChartTypeModel
,nDimensionCount
);
2959 else if( aChartType
.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR
) )
2960 pRet
= new BarChart(xChartTypeModel
,nDimensionCount
);
2961 else if( aChartType
.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_HISTOGRAM
) )
2962 pRet
= new HistogramChart(xChartTypeModel
, nDimensionCount
);
2963 else if( aChartType
.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA
) )
2964 pRet
= new AreaChart(xChartTypeModel
,nDimensionCount
,true);
2965 else if( aChartType
.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE
) )
2966 pRet
= new AreaChart(xChartTypeModel
,nDimensionCount
,true,true);
2967 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER
) )
2968 pRet
= new AreaChart(xChartTypeModel
,nDimensionCount
,false,true);
2969 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE
) )
2970 pRet
= new BubbleChart(xChartTypeModel
,nDimensionCount
);
2971 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE
) )
2972 pRet
= new PieChart(xChartTypeModel
,nDimensionCount
, bExcludingPositioning
);
2973 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET
) )
2974 pRet
= new NetChart(xChartTypeModel
,nDimensionCount
,true,std::make_unique
<PolarPlottingPositionHelper
>());
2975 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET
) )
2976 pRet
= new NetChart(xChartTypeModel
,nDimensionCount
,false,std::make_unique
<PolarPlottingPositionHelper
>());
2977 else if( aChartType
.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK
) )
2978 pRet
= new CandleStickChart(xChartTypeModel
,nDimensionCount
);
2980 pRet
= new AreaChart(xChartTypeModel
,nDimensionCount
,false,true);
2986 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */