tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / chart2 / source / view / charttypes / VSeriesPlotter.cxx
blobe86d248fcf7a5c29232be9401fd3eda2b75ab94e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
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>
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>
84 #include <utility>
85 #include <vcl/svapp.hxx>
86 #include <vcl/settings.hxx>
87 #include <comphelper/diagnose_ex.hxx>
88 #include <sal/log.hxx>
90 #include <functional>
91 #include <map>
92 #include <unordered_map>
95 namespace chart {
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)
106 , m_fMinimumY(0.0)
107 , m_fMaximumY(0.0)
111 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
112 : m_aSeriesVector(1)
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();
175 m_aZSlots.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" );
187 if(!pSeries)
188 return;
190 if(m_bCategoryXAxis)
192 if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
193 pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
194 else
195 pSeries->setCategoryXAxis();
197 else
199 if( m_pExplicitCategoriesProvider )
200 pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
203 if(zSlot<0 || o3tl::make_unsigned(zSlot)>=m_aZSlots.size())
205 //new z slot
206 std::vector< VDataSeriesGroup > aZSlot;
207 aZSlot.emplace_back( std::move(pSeries) );
208 m_aZSlots.push_back( std::move(aZSlot) );
210 else
212 //existing zslot
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) );
220 else
222 //x slot is already occupied
223 //y slot decides what to do:
225 VDataSeriesGroup& rYSlots = rXSlots[xSlot];
226 sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
228 if( ySlot < -1 )
230 //move all existing series in the xSlot to next slot
231 //@todo
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) );
239 else
241 //y slot is already occupied
242 //insert at given y and x position
244 //@todo
245 OSL_FAIL( "Not implemented yet");
251 drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
253 drawing::Direction3D aRet(1.0,1.0,1.0);
254 if (!m_pPosHelper)
255 return aRet;
257 drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
258 aRet.DirectionZ = aScale.DirectionZ*0.2;
259 if(aRet.DirectionZ>1.0)
260 aRet.DirectionZ=1.0;
261 if(aRet.DirectionZ>10)
262 aRet.DirectionZ=10;
263 return aRet;
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
332 , bool bYError )
334 rtl::Reference<SvxShapeGroupAnyD> &rShapeGroup =
335 bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
337 if(!rShapeGroup)
339 //create a group shape for this series and add to logic target:
340 rShapeGroup = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
342 return rShapeGroup;
346 OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
347 , sal_Int32 nPointIndex
348 , double fValue
349 , bool bAsPercentage )
351 OUString aNumber;
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;
364 else
366 nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
368 if(nNumberFormatKey<0)
369 nNumberFormatKey=0;
371 Color nLabelCol;
372 bool bColChanged;
373 aNumber = m_apNumberFormatterWrapper->getFormattedString(
374 nNumberFormatKey, fValue, nLabelCol, bColChanged );
375 //@todo: change color of label if bColChanged is true
377 else
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 );
386 return aNumber;
389 rtl::Reference<SvxShapeText> VSeriesPlotter::createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
390 , VDataSeries& rDataSeries
391 , sal_Int32 nPointIndex
392 , double fValue
393 , double fSumValue
394 , const awt::Point& rScreenPosition2D
395 , LabelAlignment eAlignment
396 , sal_Int32 nOffset
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 );
433 if( !pLabel )
434 return xTextShape;
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 ) );
446 if( xProps.is() )
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_ );
467 else
468 xSymbol = VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_ );
471 //prepare text
472 bool bTextWrap = false;
473 OUString aSeparator(u" "_ustr);
474 double fRotationDegrees = 0.0;
477 uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
478 if(xPointProps.is())
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;
501 aSeparator = "";
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 );
511 break;
513 case DataPointCustomLabelFieldType_CATEGORYNAME:
515 pTextList[i] = getCategoryName( nPointIndex );
516 break;
518 case DataPointCustomLabelFieldType_SERIESNAME:
520 OUString aRole;
521 if ( m_xChartTypeModel )
522 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
523 const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
524 pTextList[i] = xSeries->getLabelForRole( aRole );
525 break;
527 case DataPointCustomLabelFieldType_PERCENTAGE:
529 if(fSumValue == 0.0)
530 fSumValue = 1.0;
531 fValue /= fSumValue;
532 if(fValue < 0)
533 fValue *= -1.0;
535 pTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
536 break;
538 case DataPointCustomLabelFieldType_CELLRANGE:
540 if (aCustomLabels[i]->getDataLabelsRange())
541 pTextList[i] = aCustomLabels[i]->getString();
542 else
543 pTextList[i] = OUString();
544 break;
546 case DataPointCustomLabelFieldType_CELLREF:
548 // TODO: for now doesn't show placeholder
549 pTextList[i] = OUString();
550 break;
552 case DataPointCustomLabelFieldType_TEXT:
554 pTextList[i] = aCustomLabels[i]->getString();
555 break;
557 case DataPointCustomLabelFieldType_NEWLINE:
559 pTextList[i] = "\n";
560 break;
562 default:
563 break;
565 aCustomLabels[i]->setString( aTextList[i] );
568 else
570 auto pTextList = aTextList.getArray();
571 if( pLabel->ShowCategoryName )
573 pTextList[0] = getCategoryName( nPointIndex );
576 if( pLabel->ShowSeriesName )
578 OUString aRole;
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 )
592 if(fSumValue==0.0)
593 fSumValue=1.0;
594 fValue /= fSumValue;
595 if( fValue < 0 )
596 fValue*=-1.0;
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 ) )
614 return xTextShape;
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));
630 // create text shape
631 xTextShape = ShapeFactory::
632 createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
633 ShapeFactory::makeTransformation( aScreenPosition2D ) );
635 else
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] );
651 //create text shape
652 xTextShape = ShapeFactory::
653 createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
654 ShapeFactory::makeTransformation( aScreenPosition2D ) );
657 if( !xTextShape.is() )
658 return xTextShape;
660 // we need to use a default value for the maximum width property ?
661 if( nTextWidth == 0 && bTextWrap )
663 sal_Int32 nMinSize =
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
734 // slightly changed.
735 if( xSymbol.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", "" );
777 return xTextShape;
780 namespace
782 double lcl_getErrorBarLogicLength(
783 const uno::Sequence< double > & rData,
784 const uno::Reference< beans::XPropertySet >& xProp,
785 sal_Int32 nErrorBarStyle,
786 sal_Int32 nIndex,
787 bool bPositive,
788 bool bYError )
790 double fResult = std::numeric_limits<double>::quiet_NaN();
793 switch( nErrorBarStyle )
795 case css::chart::ErrorBarStyle::NONE:
796 break;
797 case css::chart::ErrorBarStyle::VARIANCE:
798 fResult = StatisticsHelper::getVariance( rData );
799 break;
800 case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
801 fResult = StatisticsHelper::getStandardDeviation( rData );
802 break;
803 case css::chart::ErrorBarStyle::RELATIVE:
805 double fPercent = 0;
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;
818 break;
819 case css::chart::ErrorBarStyle::ABSOLUTE:
820 xProp->getPropertyValue( bPositive
821 ? u"PositiveError"_ustr
822 : u"NegativeError"_ustr ) >>= fResult;
823 break;
824 case css::chart::ErrorBarStyle::ERROR_MARGIN:
826 // todo: check if this is really what's called error-margin
827 double fPercent = 0;
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)
835 if(fMaxValue < d)
836 fMaxValue = d;
838 if( std::isfinite( fMaxValue ) &&
839 std::isfinite( fPercent ))
841 fResult = fMaxValue * fPercent / 100.0;
845 break;
846 case css::chart::ErrorBarStyle::STANDARD_ERROR:
847 fResult = StatisticsHelper::getStandardError( rData );
848 break;
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);
856 break;
859 catch( const uno::Exception & )
861 TOOLS_WARN_EXCEPTION("chart2", "" );
864 return fResult;
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
889 , bool bYError )
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();
902 if( bYError )
904 //main direction has constant x value
905 MinX = rUnscaledLogicPosition.PositionX;
906 MaxX = rUnscaledLogicPosition.PositionX;
908 else
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() )
923 //@todo
925 return aMainDirection;
928 drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
929 , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
931 if(!pPosHelper)
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
945 , sal_Int32 nIndex
946 , bool bYError /* = true */
947 , const double* pfScaledLogicX
950 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
951 return;
953 if( ! xErrorBarProperties.is())
954 return;
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)
967 return;
969 if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
970 return;
972 if (!m_pPosHelper)
973 return;
975 drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
976 if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
978 if (bYError)
979 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
980 else
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;
991 if( pfScaledLogicX )
992 fScaledX = *pfScaledLogicX;
993 else
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() );
1003 if( bShowPositive )
1005 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
1006 if( std::isfinite( fLength ) )
1008 double fLocalX = fX;
1009 double fLocalY = fY;
1010 if( bYError )
1012 fLocalY+=fLength;
1013 aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1015 else
1017 fLocalX+=fLength;
1018 aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1020 bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
1022 else
1023 bShowPositive = false;
1026 if( bShowNegative )
1028 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
1029 if( std::isfinite( fLength ) )
1031 double fLocalX = fX;
1032 double fLocalY = fY;
1033 if( bYError )
1035 fLocalY-=fLength;
1036 aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1038 else
1040 fLocalX-=fLength;
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);
1047 } else {
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;
1059 else
1060 bShowNegative = false;
1063 if(!bShowPositive && !bShowNegative)
1064 return;
1066 std::vector<std::vector<css::drawing::Position3D>> aPoly;
1068 sal_Int32 nSequenceIndex=0;
1069 if( bShowNegative )
1070 AddPointToPoly( aPoly, aNegative, nSequenceIndex );
1071 AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
1072 if( bShowPositive )
1073 AddPointToPoly( aPoly, aPositive, nSequenceIndex );
1075 if( bShowNegative && bCreateNegativeBorder )
1077 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
1078 nSequenceIndex++;
1079 lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
1081 if( bShowPositive && bCreatePositiveBorder )
1083 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
1084 nSequenceIndex++;
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(
1106 rTarget, aPoly );
1107 PropertyMapper::setMappedProperties( *xShape, rErrorBorderProp,
1108 PropertyMapper::getPropertyNameMapForLineProperties() );
1111 void VSeriesPlotter::createErrorRectangle(
1112 const drawing::Position3D& rUnscaledLogicPosition
1113 ,VDataSeries& rVDataSeries
1114 ,sal_Int32 nIndex
1115 ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
1116 ,bool bUseXErrorData
1117 ,bool bUseYErrorData )
1119 if ( m_nDimension != 2 )
1120 return;
1122 // error border properties
1123 Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
1124 if ( bUseXErrorData )
1126 xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
1127 if ( !xErrorBorderPropX.is() )
1128 return;
1130 rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesX =
1131 getErrorBarsGroupShape( rVDataSeries, rTarget, false );
1133 if ( bUseYErrorData )
1135 xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
1136 if ( !xErrorBorderPropY.is() )
1137 return;
1139 rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesY =
1140 getErrorBarsGroupShape( rVDataSeries, rTarget, true );
1142 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
1143 return;
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 )
1174 return;
1176 if ( !m_pPosHelper )
1177 return;
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() );
1194 double fPosX = 0.0;
1195 double fPosY = 0.0;
1196 double fNegX = 0.0;
1197 double fNegY = 0.0;
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 ) ) )
1222 return;
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 )
1234 // top border
1235 addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
1237 // bottom border
1238 addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
1241 if ( bUseYErrorData )
1243 // left border
1244 addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
1246 // right border
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 )
1260 if(m_nDimension!=2)
1261 return;
1262 // error bars
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 */
1273 , nullptr );
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 )
1282 if(m_nDimension!=2)
1283 return;
1284 // error bars
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 */
1295 , pfScaledLogicX );
1299 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
1300 const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
1301 const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
1302 bool bMaySkipPoints )
1304 if(m_nDimension!=2)
1305 return;
1306 const rtl::Reference< DataSeries >& xContainer( rVDataSeries.getModel() );
1307 if(!xContainer.is())
1308 return;
1310 if (!m_pPosHelper)
1311 return;
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())
1320 continue;
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;
1352 if( !bAverageLine )
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 )
1369 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
1408 if(!bAverageLine)
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;
1417 nRealPointCount++;
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,
1451 aDefaultPos );
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 );
1464 if ( indexSep < 0 )
1465 indexSep = nStringLength;
1466 sal_Int32 nLineLength = indexSep - i;
1467 if ( nLineLength > nMaxLineLength )
1468 nMaxLineLength = nLineLength;
1469 i = indexSep;
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())
1484 return;
1486 bool bShowEquation = false;
1487 bool bShowCorrCoeff = false;
1488 if(!(( xEquationProperties->getPropertyValue( u"ShowEquation"_ustr) >>= bShowEquation ) &&
1489 ( xEquationProperties->getPropertyValue( u"ShowCorrelationCoefficient"_ustr) >>= bShowCorrCoeff )))
1490 return;
1492 if( ! (bShowEquation || bShowCorrCoeff))
1493 return;
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) )
1505 aXName = u"x"_ustr;
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;
1514 if( bShowEquation )
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 );
1524 else
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)
1540 Color nLabelCol;
1541 bool bColChanged;
1542 aFormula.append(
1543 m_apNumberFormatterWrapper->getFormattedString(
1544 nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
1545 //@todo: change color of label if bColChanged is true
1547 else
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 ));
1567 else
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;
1588 if( aPos.X < 0 )
1590 aPos.X = 0;
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 )
1599 nFormulaWidth = 0;
1602 if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
1603 aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
1604 if( aPos.Y < 0 )
1605 aPos.Y = 0;
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)
1623 return nRet;
1625 const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
1626 if (rDateCategories.empty())
1627 return nRet;
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); });
1632 if (aIt == aEnd)
1633 return nRet;
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));
1640 ++aIt;
1641 for(;aIt!=aEnd;++aIt)
1643 if (std::isnan(*aIt))
1644 continue;
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 )
1658 break;
1659 aPrevious=aCurrent;
1662 return nRet;
1664 double VSeriesPlotter::getMinimumX()
1666 double fMinimum, fMaximum;
1667 getMinimumAndMaximumX( fMinimum, fMaximum );
1668 return fMinimum;
1670 double VSeriesPlotter::getMaximumX()
1672 double fMinimum, fMaximum;
1673 getMinimumAndMaximumX( fMinimum, fMaximum );
1674 return 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 );
1683 return fMinY;
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();
1706 return fMinimum;
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 );
1715 return fMaxY;
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();
1738 return fMaximum;
1741 double VSeriesPlotter::getMinimumZ()
1743 //this is the default for all charts without a meaningful z axis
1744 return 1.0;
1746 double VSeriesPlotter::getMaximumZ()
1748 if( m_nDimension!=3 || m_aZSlots.empty() )
1749 return getMinimumZ()+1;
1750 return m_aZSlots.size();
1753 namespace
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
1841 sal_Int32 nRet = 0;
1843 for (auto const& ZSlot : m_aZSlots)
1845 for (auto const& XSlot : ZSlot)
1847 sal_Int32 nPointCount = XSlot.getPointCount();
1848 if( nPointCount>nRet )
1849 nRet = nPointCount;
1852 return 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;
1876 sal_Int32 nRet = 0;
1878 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1880 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1881 if( nPointCount>nRet )
1882 nRet = nPointCount;
1884 m_nMaxPointCount=nRet;
1885 m_aListOfCachedYValues.clear();
1886 m_aListOfCachedYValues.resize(m_nMaxPointCount);
1887 m_bMaxPointCountDirty=false;
1888 return nRet;
1891 sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1893 sal_Int32 nRet = 0;
1895 if (!m_aSeriesVector.empty())
1896 nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
1898 return nRet;
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) )
1914 continue;
1915 if(rfMaximum<fX)
1916 rfMaximum=fX;
1917 if(rfMinimum>fX)
1918 rfMinimum=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();
1927 namespace {
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
1932 * stacked.
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
1941 * values.</p>
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;
1950 size_t mnCurSeries;
1952 public:
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.
1960 if (!pStore)
1961 // This shouldn't happen!
1962 return;
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.
1969 if (fY < r.first)
1970 r.first = fY;
1971 if (r.second < fY)
1972 r.second = fY;
1974 else
1976 // No existing pair. Insert a new one.
1977 pStore->insert(
1978 it, SeriesMinMaxType::value_type(
1979 mnCurSeries, MinMaxType(fY,fY)));
1983 void getTotalRange(double& rfMin, double& rfMax) const
1985 TotalStoreType aStore;
1986 getTotalStore(aStore);
1988 if (aStore.empty())
1990 rfMin = std::numeric_limits<double>::quiet_NaN();
1991 rfMax = std::numeric_limits<double>::quiet_NaN();
1992 return;
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;
2007 private:
2009 * Parse all data and reduce them into a set of global Y value ranges per
2010 * X value.
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));
2027 else
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{}));
2048 if (!r.second)
2049 // insertion failed.
2050 return nullptr;
2052 it = r.first;
2055 return &it->second;
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.
2069 return;
2071 PerXMinMaxCalculator aRangeCalc;
2072 for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
2074 if (!pSeries)
2075 continue;
2077 for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
2079 if (nAxisIndex != pSeries->getAttachedAxisIndex())
2080 continue;
2082 double fX = pSeries->getXValue(i);
2083 if (std::isnan(fX))
2084 continue;
2086 if (fX < fMinX || fX > fMaxX)
2087 // Outside specified X range. Skip it.
2088 continue;
2090 double fY = pSeries->getYValue(i);
2091 if (std::isnan(fY))
2092 continue;
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())
2113 return;
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;
2121 return;
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() )
2135 continue;
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;
2144 else
2145 fPositiveSum += fValueMaxY;
2147 if( fValueMinY < 0 )
2149 if(std::isnan( fNegativeSum ))
2150 fNegativeSum = fFirstNegativeY = fValueMinY;
2151 else
2152 fNegativeSum += fValueMinY;
2155 rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
2156 rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
2158 else
2160 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2162 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2163 continue;
2165 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2166 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2168 if( std::isnan( fTotalSum ) )
2170 rfMinimumY = fValueMinY;
2171 rfMaximumY = fTotalSum = fValueMaxY;
2173 else
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)
2204 return;
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 )
2235 if( nAxisIndex<1 )
2236 return;
2238 m_aSecondaryValueScales[nAxisIndex]=rScale;
2241 PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
2243 PlottingPositionHelper* pRet = nullptr;
2244 if(nAxisIndex>0)
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();
2261 if( !pRet )
2262 pRet = m_pMainPosHelper;
2263 pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
2264 return *pRet;
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();
2280 if (pSeries)
2281 return pSeries;
2285 return nullptr;
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];
2298 return OUString();
2301 namespace {
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
2308 // charts).
2309 struct ROrderPair
2311 ROrderPair(OUString n, sal_Int32 r) : chartName(std::move(n)), renderOrder(r) {}
2313 OUString chartName;
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)
2330 } // unnamed
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()");
2341 return 0;
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());
2355 return aAllSeries;
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());
2370 return aAllSeries;
2373 uno::Sequence<OUString> VSeriesPlotter::getSeriesNames() const
2375 std::vector<OUString> aRetVector;
2377 OUString aRole;
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 );
2390 if( xSeries.is() )
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;
2405 OUString aRole;
2406 if (m_xChartTypeModel.is())
2407 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2409 for (VDataSeries const* pSeries : getAllSeries())
2411 if (pSeries)
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;
2463 if( xTarget.is() )
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)
2488 if (!pSeries)
2489 continue;
2491 // "ShowLegendEntry"
2492 if (!pSeries->getModel()->getFastPropertyValue(PROP_DATASERIES_SHOW_LEGEND_ENTRY).get<sal_Bool>())
2494 continue;
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())
2506 bBreak = true;
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;
2513 if ( bSwapXAndY )
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 );
2524 if (bReverse)
2525 aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
2526 else
2527 aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
2529 if (bBreak)
2530 return aResult;
2535 return aResult;
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;
2544 if( xTarget.is() )
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)
2555 if (!pSeries)
2556 continue;
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())
2564 bBreak = true;
2566 bFirstSeries = false;
2568 aResult.insert(aResult.end(), aSeriesSymbols.begin(), aSeriesSymbols.end());
2570 if (bBreak)
2571 return aResult;
2576 return aResult;
2579 namespace
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 )
2617 return aRet;
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 ) )
2630 bHasLines = true;
2631 if( bCurrentDashed )
2633 bHasDashedLines = true;
2634 break;
2638 bool bRegressionHasDashedLines=false;
2639 if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
2641 bHasLines = true;
2642 if( bRegressionHasDashedLines )
2644 bHasDashedLines = true;
2645 break;
2649 if( bHasLines )
2651 if( bHasDashedLines )
2652 aRet = awt::Size(1600,-1);
2653 else
2654 aRet = awt::Size(800,-1);
2656 return aRet;
2659 uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
2661 return uno::Any();
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;
2682 break;
2683 default:
2684 break;
2686 rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2687 xTarget, eLegendSymbolStyle,
2688 rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol );
2690 return xShape;
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;
2712 break;
2713 default:
2714 break;
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 );
2732 if( xChild.is())
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 );
2744 return xShape;
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() ) )
2758 return aResult;
2762 ViewLegendEntry aEntry;
2763 OUString aLabelText;
2764 bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
2765 bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
2766 CHART2_SERVICE_NAME_CHARTTYPE_PIE);
2769 if (bIsPie)
2771 bool bDonut = false;
2772 // "UseRings"
2773 if ((m_xChartTypeModel->getFastPropertyValue(PROP_PIECHARTTYPE_USE_RINGS) >>= bDonut) && bDonut)
2774 bIsPie = false;
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;
2803 break;
2806 if (deletedLegendEntry)
2807 continue;
2809 // symbol
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
2817 if( xShape.is() )
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 );
2827 // label
2828 aLabelText = aCategoryNames[nIdx];
2829 if( xShape.is() || !aLabelText.isEmpty() )
2831 aEntry.xLabel = FormattedStringHelper::createFormattedString( aLabelText, xTextProperties );
2832 aResult.push_back(aEntry);
2836 else
2838 // symbol
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
2846 if( xShape.is())
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 );
2855 // label
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 ))
2865 return aResult;
2867 const rtl::Reference< DataSeries >& xRegrCont = rSeries.getModel();
2868 if( xRegrCont.is())
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 )
2874 //label
2875 OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
2876 replaceParamterInString( aResStr, u"%SERIESNAME", aLabelText );
2877 aEntry.xLabel = FormattedStringHelper::createFormattedString( aResStr, xTextProperties );
2879 // symbol
2880 rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
2882 // create the symbol
2883 rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2884 xSymbolGroup, LegendSymbolStyle::Line,
2885 aCurves[i],
2886 VLegendSymbolFactory::PropertyType::Line, uno::Any() );
2888 // set CID to symbol for selection
2889 if( xShape.is())
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" );
2909 return aResult;
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()))
2921 return aResult;
2925 ViewLegendSymbol aEntry;
2926 // symbol
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
2933 if (xShape.is())
2935 aEntry.xSymbol = std::move(xSymbolGroup);
2936 aResult.push_back(aEntry);
2939 catch (const uno::Exception &)
2941 DBG_UNHANDLED_EXCEPTION("chart2" );
2943 return aResult;
2946 VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
2947 const rtl::Reference<ChartType>& xChartTypeModel
2948 , sal_Int32 nDimensionCount
2949 , bool bExcludingPositioning )
2951 if (!xChartTypeModel.is())
2952 return nullptr;
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);
2979 else
2980 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2981 return pRet;
2984 } //namespace chart
2986 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */