tdf#94235 Add support for series name in data series labels
[LibreOffice.git] / chart2 / source / view / charttypes / VSeriesPlotter.cxx
blobc235148a204541c7ff7d1edd94665a633108c1fc
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 <memory>
21 #include <VSeriesPlotter.hxx>
22 #include <BaseGFXHelper.hxx>
23 #include <VLineProperties.hxx>
24 #include <ShapeFactory.hxx>
26 #include <CommonConverters.hxx>
27 #include <ExplicitCategoriesProvider.hxx>
28 #include <ObjectIdentifier.hxx>
29 #include <StatisticsHelper.hxx>
30 #include <PlottingPositionHelper.hxx>
31 #include <LabelPositionHelper.hxx>
32 #include <ChartTypeHelper.hxx>
33 #include <Clipping.hxx>
34 #include <servicenames_charttypes.hxx>
35 #include <NumberFormatterWrapper.hxx>
36 #include <DataSeriesHelper.hxx>
37 #include <RegressionCurveHelper.hxx>
38 #include <VLegendSymbolFactory.hxx>
39 #include <FormattedStringHelper.hxx>
40 #include <RelativePositionHelper.hxx>
41 #include <DateHelper.hxx>
42 #include <DiagramHelper.hxx>
43 #include <defines.hxx>
44 #include <ChartModel.hxx>
46 //only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
47 #include "BarChart.hxx"
48 #include "PieChart.hxx"
49 #include "AreaChart.hxx"
50 #include "CandleStickChart.hxx"
51 #include "BubbleChart.hxx"
52 #include "NetChart.hxx"
53 #include <unonames.hxx>
54 #include <SpecialCharacters.hxx>
56 #include <com/sun/star/chart2/DataPointLabel.hpp>
57 #include <com/sun/star/chart/ErrorBarStyle.hpp>
58 #include <com/sun/star/chart/TimeUnit.hpp>
59 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
60 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
61 #include <com/sun/star/container/XChild.hpp>
62 #include <com/sun/star/chart2/RelativePosition.hpp>
63 #include <tools/color.hxx>
64 #include <tools/UnitConversion.hxx>
65 #include <rtl/ustrbuf.hxx>
66 #include <rtl/math.hxx>
67 #include <basegfx/vector/b2dvector.hxx>
68 #include <com/sun/star/drawing/LineStyle.hpp>
69 #include <com/sun/star/util/XCloneable.hpp>
70 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
72 #include <com/sun/star/drawing/XShapes.hpp>
74 #include <unotools/localedatawrapper.hxx>
75 #include <comphelper/sequence.hxx>
76 #include <vcl/svapp.hxx>
77 #include <vcl/settings.hxx>
78 #include <tools/diagnose_ex.h>
79 #include <sal/log.hxx>
81 #include <functional>
82 #include <map>
83 #include <unordered_map>
86 namespace chart {
88 using namespace ::com::sun::star;
89 using namespace ::com::sun::star::chart;
90 using namespace ::com::sun::star::chart2;
91 using ::com::sun::star::uno::Reference;
92 using ::com::sun::star::uno::Sequence;
94 VDataSeriesGroup::CachedYValues::CachedYValues()
95 : m_bValuesDirty(true)
96 , m_fMinimumY(0.0)
97 , m_fMaximumY(0.0)
101 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
102 : m_aSeriesVector(1)
103 , m_bMaxPointCountDirty(true)
104 , m_nMaxPointCount(0)
105 , m_aListOfCachedYValues()
107 m_aSeriesVector[0] = std::move(pSeries);
110 VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
111 : m_aSeriesVector( std::move(other.m_aSeriesVector) )
112 , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
113 , m_nMaxPointCount( other.m_nMaxPointCount )
114 , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
118 VDataSeriesGroup::~VDataSeriesGroup()
122 void VDataSeriesGroup::deleteSeries()
124 //delete all data series help objects:
125 m_aSeriesVector.clear();
128 void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
130 m_aSeriesVector.push_back(std::move(pSeries));
131 m_bMaxPointCountDirty=true;
134 sal_Int32 VDataSeriesGroup::getSeriesCount() const
136 return m_aSeriesVector.size();
139 VSeriesPlotter::VSeriesPlotter( const uno::Reference<XChartType>& xChartTypeModel
140 , sal_Int32 nDimensionCount, bool bCategoryXAxis )
141 : PlotterBase( nDimensionCount )
142 , m_pMainPosHelper( nullptr )
143 , m_xChartTypeModel(xChartTypeModel)
144 , m_xChartTypeModelProps( uno::Reference< beans::XPropertySet >::query( xChartTypeModel ))
145 , m_aZSlots()
146 , m_bCategoryXAxis(bCategoryXAxis)
147 , m_nTimeResolution(css::chart::TimeUnit::DAY)
148 , m_aNullDate(30,12,1899)
149 , m_xColorScheme()
150 , m_pExplicitCategoriesProvider(nullptr)
151 , m_bPointsWereSkipped(false)
152 , m_bPieLabelsAllowToMove(false)
153 , m_aAvailableOuterRect(0, 0, 0, 0)
155 SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
158 VSeriesPlotter::~VSeriesPlotter()
160 //delete all data series help objects:
161 for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
163 for (VDataSeriesGroup & rGroup : rGroupVector)
165 rGroup.deleteSeries();
167 rGroupVector.clear();
169 m_aZSlots.clear();
171 m_aSecondaryPosHelperMap.clear();
173 m_aSecondaryValueScales.clear();
176 void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
178 //take ownership of pSeries
180 OSL_PRECOND( pSeries, "series to add is NULL" );
181 if(!pSeries)
182 return;
184 if(m_bCategoryXAxis)
186 if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
187 pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
188 else
189 pSeries->setCategoryXAxis();
191 else
193 if( m_pExplicitCategoriesProvider )
194 pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
197 if(zSlot<0 || zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
199 //new z slot
200 std::vector< VDataSeriesGroup > aZSlot;
201 aZSlot.emplace_back( std::move(pSeries) );
202 m_aZSlots.push_back( std::move(aZSlot) );
204 else
206 //existing zslot
207 std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
209 if(xSlot<0 || xSlot>=static_cast<sal_Int32>(rXSlots.size()))
211 //append the series to already existing x series
212 rXSlots.emplace_back( std::move(pSeries) );
214 else
216 //x slot is already occupied
217 //y slot decides what to do:
219 VDataSeriesGroup& rYSlots = rXSlots[xSlot];
220 sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
222 if( ySlot < -1 )
224 //move all existing series in the xSlot to next slot
225 //@todo
226 OSL_FAIL( "Not implemented yet");
228 else if( ySlot == -1 || ySlot >= nYSlotCount)
230 //append the series to already existing y series
231 rYSlots.addSeries( std::move(pSeries) );
233 else
235 //y slot is already occupied
236 //insert at given y and x position
238 //@todo
239 OSL_FAIL( "Not implemented yet");
245 drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
247 drawing::Direction3D aRet(1.0,1.0,1.0);
248 if (!m_pPosHelper)
249 return aRet;
251 drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
252 aRet.DirectionZ = aScale.DirectionZ*0.2;
253 if(aRet.DirectionZ>1.0)
254 aRet.DirectionZ=1.0;
255 if(aRet.DirectionZ>10)
256 aRet.DirectionZ=10;
257 return aRet;
260 void VSeriesPlotter::releaseShapes()
262 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
264 for (VDataSeriesGroup const & rGroup : rGroupVector)
266 //iterate through all series in this x slot
267 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
269 pSeries->releaseShapes();
275 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
276 , const uno::Reference< drawing::XShapes >& xTarget )
278 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xGroupShape );
279 if( !xShapes.is() )
281 //create a group shape for this series and add to logic target:
282 xShapes = createGroupShape( xTarget,pDataSeries->getCID() );
283 pDataSeries->m_xGroupShape = xShapes;
285 return xShapes;
288 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
289 , const uno::Reference< drawing::XShapes >& xTarget )
291 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xFrontSubGroupShape );
292 if(!xShapes.is())
294 //ensure that the series group shape is already created
295 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
296 //ensure that the back child is created first
297 getSeriesGroupShapeBackChild( pDataSeries, xTarget );
298 //use series group shape as parent for the new created front group shape
299 xShapes = createGroupShape( xSeriesShapes );
300 pDataSeries->m_xFrontSubGroupShape = xShapes;
302 return xShapes;
305 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
306 , const uno::Reference< drawing::XShapes >& xTarget )
308 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xBackSubGroupShape );
309 if(!xShapes.is())
311 //ensure that the series group shape is already created
312 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
313 //use series group shape as parent for the new created back group shape
314 xShapes = createGroupShape( xSeriesShapes );
315 pDataSeries->m_xBackSubGroupShape = xShapes;
317 return xShapes;
320 uno::Reference< drawing::XShapes > VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
321 , const uno::Reference< drawing::XShapes >& xTextTarget )
323 //xTextTarget needs to be a 2D shape container always!
325 uno::Reference< drawing::XShapes > xShapes( rDataSeries.m_xLabelsGroupShape );
326 if(!xShapes.is())
328 //create a 2D group shape for texts of this series and add to text target:
329 xShapes = m_pShapeFactory->createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
330 rDataSeries.m_xLabelsGroupShape = xShapes;
332 return xShapes;
335 uno::Reference< drawing::XShapes > VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
336 , const uno::Reference< drawing::XShapes >& xTarget
337 , bool bYError )
339 uno::Reference< css::drawing::XShapes > &rShapeGroup =
340 bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
342 uno::Reference< drawing::XShapes > xShapes( rShapeGroup );
343 if(!xShapes.is())
345 //create a group shape for this series and add to logic target:
346 xShapes = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
347 rShapeGroup = xShapes;
349 return xShapes;
353 OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
354 , sal_Int32 nPointIndex
355 , double fValue
356 , bool bAsPercentage )
358 OUString aNumber;
360 if (m_apNumberFormatterWrapper)
362 sal_Int32 nNumberFormatKey = 0;
363 if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
364 nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
365 else if( bAsPercentage )
367 sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
368 if( nPercentFormat != -1 )
369 nNumberFormatKey = nPercentFormat;
371 else
373 nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
375 if(nNumberFormatKey<0)
376 nNumberFormatKey=0;
378 Color nLabelCol;
379 bool bColChanged;
380 aNumber = m_apNumberFormatterWrapper->getFormattedString(
381 nNumberFormatKey, fValue, nLabelCol, bColChanged );
382 //@todo: change color of label if bColChanged is true
384 else
386 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
387 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
388 assert(aNumDecimalSep.getLength() > 0);
389 sal_Unicode cDecSeparator = aNumDecimalSep[0];
390 aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
391 , 3/*DecPlaces*/ , cDecSeparator );
393 return aNumber;
396 uno::Reference< drawing::XShape > VSeriesPlotter::createDataLabel( const uno::Reference< drawing::XShapes >& xTarget
397 , VDataSeries& rDataSeries
398 , sal_Int32 nPointIndex
399 , double fValue
400 , double fSumValue
401 , const awt::Point& rScreenPosition2D
402 , LabelAlignment eAlignment
403 , sal_Int32 nOffset
404 , sal_Int32 nTextWidth )
406 uno::Reference< drawing::XShape > xTextShape;
407 Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
411 const uno::Reference< css::beans::XPropertySet >& xPropertySet(
412 rDataSeries.getPropertiesOfPoint( nPointIndex ) );
413 if( xPropertySet.is() )
415 uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
416 if( aAny.hasValue() )
418 aAny >>= aCustomLabels;
422 awt::Point aScreenPosition2D(rScreenPosition2D);
423 if(eAlignment==LABEL_ALIGN_LEFT)
424 aScreenPosition2D.X -= nOffset;
425 else if(eAlignment==LABEL_ALIGN_RIGHT)
426 aScreenPosition2D.X += nOffset;
427 else if(eAlignment==LABEL_ALIGN_TOP)
428 aScreenPosition2D.Y -= nOffset;
429 else if(eAlignment==LABEL_ALIGN_BOTTOM)
430 aScreenPosition2D.Y += nOffset;
432 uno::Reference< drawing::XShapes > xTarget_ =
433 m_pShapeFactory->createGroup2D(
434 getLabelsGroupShape(rDataSeries, xTarget),
435 ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
437 //check whether the label needs to be created and how:
438 DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
440 if( !pLabel )
441 return xTextShape;
443 //prepare legend symbol
445 // get the font size for the label through the "CharHeight" property
446 // attached to the passed data series entry.
447 // (By tracing font height values it results that for pie chart the
448 // font size is not the same for all labels, but here no font size
449 // modification occurs).
450 float fViewFontSize( 10.0 );
452 uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
453 if( xProps.is() )
454 xProps->getPropertyValue( "CharHeight") >>= fViewFontSize;
455 fViewFontSize = convertPointToMm100(fViewFontSize);
458 // the font height is used for computing the size of an optional legend
459 // symbol to be prepended to the text label.
460 Reference< drawing::XShape > xSymbol;
461 if(pLabel->ShowLegendSymbol)
463 sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
464 awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
465 sal_Int32 nSymbolWidth = aCurrentRatio.Width;
466 if( aCurrentRatio.Height > 0 )
468 nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
470 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
472 if( rDataSeries.isVaryColorsByPoint() )
473 xSymbol.set( VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_, m_xShapeFactory ) );
474 else
475 xSymbol.set( VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_, m_xShapeFactory ) );
478 //prepare text
479 bool bTextWrap = false;
480 OUString aSeparator(" ");
481 double fRotationDegrees = 0.0;
484 uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
485 if(xPointProps.is())
487 xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
488 xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
489 // Extract the optional text rotation through the
490 // "TextRotation" property attached to the passed data point.
491 xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
494 catch( const uno::Exception& )
496 TOOLS_WARN_EXCEPTION("chart2", "" );
499 sal_Int32 nLineCountForSymbolsize = 0;
500 sal_uInt32 nTextListLength = 4;
501 sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
502 Sequence< OUString > aTextList( nTextListLength );
504 bool bUseCustomLabel = nCustomLabelsCount > 0;
505 if( bUseCustomLabel )
507 nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
508 aSeparator = "";
509 aTextList = Sequence< OUString >( nTextListLength );
510 for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
512 switch( aCustomLabels[i]->getFieldType() )
514 case DataPointCustomLabelFieldType_VALUE:
516 aTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
517 break;
519 case DataPointCustomLabelFieldType_CATEGORYNAME:
521 aTextList[i] = getCategoryName( nPointIndex );
522 break;
524 case DataPointCustomLabelFieldType_SERIESNAME:
526 OUString aRole;
527 if ( m_xChartTypeModel )
528 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
529 const uno::Reference< XDataSeries >& xSeries( rDataSeries.getModel() );
530 aTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
531 break;
533 case DataPointCustomLabelFieldType_PERCENTAGE:
535 if(fSumValue == 0.0)
536 fSumValue = 1.0;
537 fValue /= fSumValue;
538 if(fValue < 0)
539 fValue *= -1.0;
541 aTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
542 break;
544 case DataPointCustomLabelFieldType_CELLREF:
545 case DataPointCustomLabelFieldType_CELLRANGE:
547 // TODO: for now doesn't show placeholder
548 aTextList[i] = OUString();
549 break;
551 case DataPointCustomLabelFieldType_TEXT:
553 aTextList[i] = aCustomLabels[i]->getString();
554 break;
556 case DataPointCustomLabelFieldType_NEWLINE:
558 aTextList[i] = "\n";
559 break;
561 default:
562 break;
564 aCustomLabels[i]->setString( aTextList[i] );
567 else
569 if( pLabel->ShowCategoryName )
571 aTextList[0] = getCategoryName( nPointIndex );
574 if( pLabel->ShowSeriesName )
576 OUString aRole;
577 if ( m_xChartTypeModel )
578 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
579 const uno::Reference< XDataSeries >& xSeries( rDataSeries.getModel() );
580 aTextList[1] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
583 if( pLabel->ShowNumber )
585 aTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
588 if( pLabel->ShowNumberInPercent )
590 if(fSumValue==0.0)
591 fSumValue=1.0;
592 fValue /= fSumValue;
593 if( fValue < 0 )
594 fValue*=-1.0;
596 aTextList[3] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
600 for( auto const & line : std::as_const(aTextList) )
602 if( !line.isEmpty() )
604 ++nLineCountForSymbolsize;
608 //prepare properties for multipropertyset-interface of shape
609 tNameSequence* pPropNames;
610 tAnySequence* pPropValues;
611 if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
612 return xTextShape;
614 // set text alignment for the text shape to be created.
615 LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
617 // check if data series entry percent value and absolute value have to
618 // be appended to the text label, and what should be the separator
619 // character (comma, space, new line). In case it is a new line we get
620 // a multi-line label.
621 bool bMultiLineLabel = ( aSeparator == "\n" );
623 if( bUseCustomLabel )
625 Sequence< uno::Reference< XFormattedString > > aFormattedLabels( aCustomLabels.getLength() );
626 for( int i = 0; i < aFormattedLabels.getLength(); i++ )
628 aFormattedLabels[i] = aCustomLabels[i];
631 // create text shape
632 xTextShape = ShapeFactory::getOrCreateShapeFactory( m_xShapeFactory )->
633 createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
634 ShapeFactory::makeTransformation( aScreenPosition2D ) );
636 else
638 // join text list elements
639 OUStringBuffer aText;
640 for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
642 if( !aTextList[nN].isEmpty() )
644 if( !aText.isEmpty() )
646 aText.append(aSeparator);
648 aText.append( aTextList[nN] );
652 //create text shape
653 xTextShape = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory)->
654 createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
655 ShapeFactory::makeTransformation( aScreenPosition2D ) );
658 if( !xTextShape.is() )
659 return xTextShape;
661 // we need to use a default value for the maximum width property ?
662 if( nTextWidth == 0 && bTextWrap )
664 sal_Int32 nMinSize =
665 (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
666 ? m_aPageReferenceSize.Height
667 : m_aPageReferenceSize.Width;
668 nTextWidth = nMinSize / 3;
671 // in case text must be wrapped set the maximum width property
672 // for the text shape
673 if( nTextWidth != 0 && bTextWrap )
675 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
676 if( xProp.is() )
678 // compute the height of a line of text
679 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
681 nLineCountForSymbolsize = 1;
683 awt::Size aTextSize = xTextShape->getSize();
684 sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
686 // set maximum text width
687 uno::Any aTextMaximumFrameWidth( nTextWidth );
688 xProp->setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
690 // compute the total lines of text
691 aTextSize = xTextShape->getSize();
692 nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
696 // in case text is rotated, the transformation property of the text
697 // shape is modified.
698 if( fRotationDegrees != 0.0 )
700 const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
701 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
702 if( xProp.is() )
703 xProp->setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
704 LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
707 awt::Point aTextShapePos(xTextShape->getPosition());
708 if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
710 awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
711 if( aRelPos.X != -1 )
713 xTextShape->setPosition(aRelPos);
714 if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
715 rDataSeries.getPropertiesOfSeries()->getPropertyValue( "ShowCustomLeaderLines" ).get<sal_Bool>())
717 sal_Int32 nX1 = rScreenPosition2D.X;
718 sal_Int32 nY1 = rScreenPosition2D.Y;
719 sal_Int32 nX2 = nX1;
720 sal_Int32 nY2 = nY1;
721 ::basegfx::B2IRectangle aRect(BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
722 if (nX1 < aRelPos.X)
723 nX2 = aRelPos.X;
724 else if (nX1 > aRect.getMaxX())
725 nX2 = aRect.getMaxX();
727 if (nY1 < aRect.getMinY())
728 nY2 = aRect.getMinY();
729 else if (nY1 > aRect.getMaxY())
730 nY2 = aRect.getMaxY();
732 //when the line is very short compared to the page size don't create one
733 ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
734 double fPageDiagonaleLength = sqrt(double(m_aPageReferenceSize.Width)*double(m_aPageReferenceSize.Width) + double(m_aPageReferenceSize.Height)*double(m_aPageReferenceSize.Height));
735 if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
737 drawing::PointSequenceSequence aPoints(1);
738 aPoints[0].realloc(2);
739 aPoints[0][0].X = nX1;
740 aPoints[0][0].Y = nY1;
741 aPoints[0][1].X = nX2;
742 aPoints[0][1].Y = nY2;
744 VLineProperties aVLineProperties;
745 m_pShapeFactory->createLine2D(xTarget, aPoints, &aVLineProperties);
751 // in case legend symbol has to be displayed, text shape position is
752 // slightly changed.
753 const awt::Point aUnrotatedTextPos(xTextShape->getPosition());
754 if( xSymbol.is() )
756 const awt::Point aOldTextPos( xTextShape->getPosition() );
757 awt::Point aNewTextPos( aOldTextPos );
759 awt::Point aSymbolPosition( aUnrotatedTextPos );
760 awt::Size aSymbolSize( xSymbol->getSize() );
761 awt::Size aTextSize = xTextShape->getSize();
763 sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
764 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
765 nLineCountForSymbolsize = 1;
766 aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
768 if(eAlignment==LABEL_ALIGN_LEFT
769 || eAlignment==LABEL_ALIGN_LEFT_TOP
770 || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
772 aSymbolPosition.X -= nXDiff;
774 else if(eAlignment==LABEL_ALIGN_RIGHT
775 || eAlignment==LABEL_ALIGN_RIGHT_TOP
776 || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
778 aNewTextPos.X += nXDiff;
780 else if(eAlignment==LABEL_ALIGN_TOP
781 || eAlignment==LABEL_ALIGN_BOTTOM
782 || eAlignment==LABEL_ALIGN_CENTER )
784 aSymbolPosition.X -= nXDiff/2;
785 aNewTextPos.X += nXDiff/2;
788 xSymbol->setPosition( aSymbolPosition );
789 xTextShape->setPosition( aNewTextPos );
792 catch( const uno::Exception& )
794 TOOLS_WARN_EXCEPTION("chart2", "" );
797 return xTextShape;
800 namespace
802 double lcl_getErrorBarLogicLength(
803 const uno::Sequence< double > & rData,
804 const uno::Reference< beans::XPropertySet >& xProp,
805 sal_Int32 nErrorBarStyle,
806 sal_Int32 nIndex,
807 bool bPositive,
808 bool bYError )
810 double fResult;
811 ::rtl::math::setNan( & fResult );
814 switch( nErrorBarStyle )
816 case css::chart::ErrorBarStyle::NONE:
817 break;
818 case css::chart::ErrorBarStyle::VARIANCE:
819 fResult = StatisticsHelper::getVariance( rData );
820 break;
821 case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
822 fResult = StatisticsHelper::getStandardDeviation( rData );
823 break;
824 case css::chart::ErrorBarStyle::RELATIVE:
826 double fPercent = 0;
827 if( xProp->getPropertyValue( bPositive
828 ? OUString("PositiveError")
829 : OUString("NegativeError") ) >>= fPercent )
831 if( nIndex >=0 && nIndex < rData.getLength() &&
832 ! std::isnan( rData[nIndex] ) &&
833 ! std::isnan( fPercent ))
835 fResult = rData[nIndex] * fPercent / 100.0;
839 break;
840 case css::chart::ErrorBarStyle::ABSOLUTE:
841 xProp->getPropertyValue( bPositive
842 ? OUString("PositiveError")
843 : OUString("NegativeError") ) >>= fResult;
844 break;
845 case css::chart::ErrorBarStyle::ERROR_MARGIN:
847 // todo: check if this is really what's called error-margin
848 double fPercent = 0;
849 if( xProp->getPropertyValue( bPositive
850 ? OUString("PositiveError")
851 : OUString("NegativeError") ) >>= fPercent )
853 double fMaxValue;
854 ::rtl::math::setInf(&fMaxValue, true);
855 for(double d : rData)
857 if(fMaxValue < d)
858 fMaxValue = d;
860 if( std::isfinite( fMaxValue ) &&
861 std::isfinite( fPercent ))
863 fResult = fMaxValue * fPercent / 100.0;
867 break;
868 case css::chart::ErrorBarStyle::STANDARD_ERROR:
869 fResult = StatisticsHelper::getStandardError( rData );
870 break;
871 case css::chart::ErrorBarStyle::FROM_DATA:
873 uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
874 if( xErrorBarData.is())
875 fResult = StatisticsHelper::getErrorFromDataSource(
876 xErrorBarData, nIndex, bPositive, bYError);
878 break;
881 catch( const uno::Exception & )
883 TOOLS_WARN_EXCEPTION("chart2", "" );
886 return fResult;
889 void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
890 , drawing::PolyPolygonShape3D& rPoly, sal_Int32 nSequenceIndex )
892 double fFixedWidth = 200.0;
894 aMainDirection.normalize();
895 ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
896 aOrthoDirection.normalize();
898 ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
899 ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
900 ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
902 AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
903 AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
906 ::basegfx::B2DVector lcl_getErrorBarMainDirection(
907 const drawing::Position3D& rStart
908 , const drawing::Position3D& rBottomEnd
909 , PlottingPositionHelper const * pPosHelper
910 , const drawing::Position3D& rUnscaledLogicPosition
911 , bool bYError )
913 ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
914 , rStart.PositionY - rBottomEnd.PositionY );
915 if( !aMainDirection.getLength() )
917 //get logic clip values:
918 double MinX = pPosHelper->getLogicMinX();
919 double MinY = pPosHelper->getLogicMinY();
920 double MaxX = pPosHelper->getLogicMaxX();
921 double MaxY = pPosHelper->getLogicMaxY();
922 double fZ = pPosHelper->getLogicMinZ();
924 if( bYError )
926 //main direction has constant x value
927 MinX = rUnscaledLogicPosition.PositionX;
928 MaxX = rUnscaledLogicPosition.PositionX;
930 else
932 //main direction has constant y value
933 MinY = rUnscaledLogicPosition.PositionY;
934 MaxY = rUnscaledLogicPosition.PositionY;
937 drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
938 drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
940 aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
941 , aStart.PositionY - aEnd.PositionY );
943 if( !aMainDirection.getLength() )
945 //@todo
947 return aMainDirection;
950 drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
951 , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
953 if(!pPosHelper)
954 return drawing::Position3D(0,0,0);
955 pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
956 pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
957 return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
960 } // anonymous namespace
962 void VSeriesPlotter::createErrorBar(
963 const uno::Reference< drawing::XShapes >& xTarget
964 , const drawing::Position3D& rUnscaledLogicPosition
965 , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
966 , const VDataSeries& rVDataSeries
967 , sal_Int32 nIndex
968 , bool bYError /* = true */
969 , const double* pfScaledLogicX
972 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
973 return;
975 if( ! xErrorBarProperties.is())
976 return;
980 bool bShowPositive = false;
981 bool bShowNegative = false;
982 sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
984 xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
985 xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
986 xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
988 if(!bShowPositive && !bShowNegative)
989 return;
991 if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
992 return;
994 if (!m_pPosHelper)
995 return;
997 drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
998 if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
1000 if (bYError)
1001 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
1002 else
1003 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
1006 bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
1007 bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
1008 drawing::Position3D aMiddle(aUnscaledLogicPosition);
1009 const double fX = aUnscaledLogicPosition.PositionX;
1010 const double fY = aUnscaledLogicPosition.PositionY;
1011 const double fZ = aUnscaledLogicPosition.PositionZ;
1012 double fScaledX = fX;
1013 if( pfScaledLogicX )
1014 fScaledX = *pfScaledLogicX;
1015 else
1016 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1018 aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
1020 drawing::Position3D aNegative(aMiddle);
1021 drawing::Position3D aPositive(aMiddle);
1023 uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
1025 if( bShowPositive )
1027 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
1028 if( std::isfinite( fLength ) )
1030 double fLocalX = fX;
1031 double fLocalY = fY;
1032 if( bYError )
1034 fLocalY+=fLength;
1035 aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1037 else
1039 fLocalX+=fLength;
1040 aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1042 bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
1044 else
1045 bShowPositive = false;
1048 if( bShowNegative )
1050 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
1051 if( std::isfinite( fLength ) )
1053 double fLocalX = fX;
1054 double fLocalY = fY;
1055 if( bYError )
1057 fLocalY-=fLength;
1058 aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1060 else
1062 fLocalX-=fLength;
1063 aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1065 bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
1067 else
1068 bShowNegative = false;
1071 if(!bShowPositive && !bShowNegative)
1072 return;
1074 drawing::PolyPolygonShape3D aPoly;
1076 sal_Int32 nSequenceIndex=0;
1077 if( bShowNegative )
1078 AddPointToPoly( aPoly, aNegative, nSequenceIndex );
1079 AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
1080 if( bShowPositive )
1081 AddPointToPoly( aPoly, aPositive, nSequenceIndex );
1083 if( bShowNegative && bCreateNegativeBorder )
1085 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
1086 nSequenceIndex++;
1087 lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
1089 if( bShowPositive && bCreatePositiveBorder )
1091 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
1092 nSequenceIndex++;
1093 lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
1096 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D( xTarget, PolyToPointSequence( aPoly) );
1097 setMappedProperties( xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
1099 catch( const uno::Exception & )
1101 TOOLS_WARN_EXCEPTION("chart2", "" );
1106 void VSeriesPlotter::addErrorBorder(
1107 const drawing::Position3D& rPos0
1108 ,const drawing::Position3D& rPos1
1109 ,const uno::Reference< drawing::XShapes >& rTarget
1110 ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
1112 drawing::PolyPolygonShape3D aPoly;
1113 sal_Int32 nSequenceIndex = 0;
1114 AddPointToPoly( aPoly, rPos0, nSequenceIndex );
1115 AddPointToPoly( aPoly, rPos1, nSequenceIndex );
1116 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1117 rTarget, PolyToPointSequence( aPoly) );
1118 setMappedProperties( xShape, rErrorBorderProp,
1119 PropertyMapper::getPropertyNameMapForLineProperties() );
1122 void VSeriesPlotter::createErrorRectangle(
1123 const drawing::Position3D& rUnscaledLogicPosition
1124 ,VDataSeries& rVDataSeries
1125 ,sal_Int32 nIndex
1126 ,const uno::Reference< drawing::XShapes >& rTarget
1127 ,bool bUseXErrorData
1128 ,bool bUseYErrorData )
1130 if ( m_nDimension != 2 )
1131 return;
1133 // error border properties
1134 Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
1135 if ( bUseXErrorData )
1137 xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
1138 if ( !xErrorBorderPropX.is() )
1139 return;
1141 uno::Reference< drawing::XShapes > xErrorBorder_ShapesX(
1142 getErrorBarsGroupShape( rVDataSeries, rTarget, false ) );
1144 if ( bUseYErrorData )
1146 xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
1147 if ( !xErrorBorderPropY.is() )
1148 return;
1150 uno::Reference< drawing::XShapes > xErrorBorder_ShapesY(
1151 getErrorBarsGroupShape( rVDataSeries, rTarget, true ) );
1153 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
1154 return;
1158 bool bShowXPositive = false;
1159 bool bShowXNegative = false;
1160 bool bShowYPositive = false;
1161 bool bShowYNegative = false;
1163 sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
1164 sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
1166 if ( bUseXErrorData )
1168 xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
1169 xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
1170 xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
1172 if ( bUseYErrorData )
1174 xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
1175 xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
1176 xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
1179 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
1180 bUseXErrorData = false;
1181 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
1182 bUseYErrorData = false;
1184 if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
1185 return;
1187 if ( !m_pPosHelper )
1188 return;
1190 drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
1191 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1192 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
1193 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1194 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
1196 const double fX = aUnscaledLogicPosition.PositionX;
1197 const double fY = aUnscaledLogicPosition.PositionY;
1198 const double fZ = aUnscaledLogicPosition.PositionZ;
1199 double fScaledX = fX;
1200 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1202 uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
1203 uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
1205 double fPosX = 0.0;
1206 double fPosY = 0.0;
1207 double fNegX = 0.0;
1208 double fNegY = 0.0;
1209 if ( bUseXErrorData )
1211 if ( bShowXPositive )
1212 fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1213 nErrorBorderStyleX, nIndex, true, false );
1214 if ( bShowXNegative )
1215 fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1216 nErrorBorderStyleX, nIndex, false, false );
1219 if ( bUseYErrorData )
1221 if ( bShowYPositive )
1222 fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1223 nErrorBorderStyleY, nIndex, true, true );
1224 if ( bShowYNegative )
1225 fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1226 nErrorBorderStyleY, nIndex, false, true );
1229 if ( !( std::isfinite( fPosX ) &&
1230 std::isfinite( fPosY ) &&
1231 std::isfinite( fNegX ) &&
1232 std::isfinite( fNegY ) ) )
1233 return;
1235 drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
1236 fX - fNegX, fY - fNegY, fZ ) );
1237 drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
1238 fX - fNegX, fY + fPosY, fZ ) );
1239 drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
1240 fX + fPosX, fY + fPosY, fZ ) );
1241 drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
1242 fX + fPosX, fY - fNegY, fZ ) );
1243 if ( bUseXErrorData )
1245 // top border
1246 addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
1248 // bottom border
1249 addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
1252 if ( bUseYErrorData )
1254 // left border
1255 addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
1257 // right border
1258 addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
1261 catch( const uno::Exception & )
1263 DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
1267 void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
1268 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1269 , const uno::Reference< drawing::XShapes >& xTarget )
1271 if(m_nDimension!=2)
1272 return;
1273 // error bars
1274 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
1275 if( xErrorBarProp.is())
1277 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1278 getErrorBarsGroupShape(rVDataSeries, xTarget, false) );
1280 createErrorBar( xErrorBarsGroup_Shapes
1281 , rUnscaledLogicPosition, xErrorBarProp
1282 , rVDataSeries, nPointIndex
1283 , false /* bYError */
1284 , nullptr );
1288 void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
1289 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1290 , const uno::Reference< drawing::XShapes >& xTarget
1291 , double const * pfScaledLogicX )
1293 if(m_nDimension!=2)
1294 return;
1295 // error bars
1296 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
1297 if( xErrorBarProp.is())
1299 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1300 getErrorBarsGroupShape(rVDataSeries, xTarget, true) );
1302 createErrorBar( xErrorBarsGroup_Shapes
1303 , rUnscaledLogicPosition, xErrorBarProp
1304 , rVDataSeries, nPointIndex
1305 , true /* bYError */
1306 , pfScaledLogicX );
1310 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
1311 const uno::Reference< drawing::XShapes >& xTarget,
1312 const uno::Reference< drawing::XShapes >& xEquationTarget,
1313 bool bMaySkipPoints )
1315 if(m_nDimension!=2)
1316 return;
1317 uno::Reference< XRegressionCurveContainer > xContainer( rVDataSeries.getModel(), uno::UNO_QUERY );
1318 if(!xContainer.is())
1319 return;
1321 if (!m_pPosHelper)
1322 return;
1324 uno::Sequence< uno::Reference< XRegressionCurve > > aCurveList = xContainer->getRegressionCurves();
1326 for(sal_Int32 nN=0; nN<aCurveList.getLength(); nN++)
1328 uno::Reference< XRegressionCurveCalculator > xCalculator( aCurveList[nN]->getCalculator() );
1329 if( !xCalculator.is())
1330 continue;
1332 uno::Reference< beans::XPropertySet > xProperties( aCurveList[nN], uno::UNO_QUERY );
1334 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurveList[nN] );
1336 sal_Int32 aDegree = 2;
1337 sal_Int32 aPeriod = 2;
1338 double aExtrapolateForward = 0.0;
1339 double aExtrapolateBackward = 0.0;
1340 bool bForceIntercept = false;
1341 double aInterceptValue = 0.0;
1343 if ( xProperties.is() && !bAverageLine )
1345 xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
1346 xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
1347 xProperties->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
1348 xProperties->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
1349 xProperties->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
1350 if (bForceIntercept)
1351 xProperties->getPropertyValue( "InterceptValue") >>= aInterceptValue;
1354 double fChartMinX = m_pPosHelper->getLogicMinX();
1355 double fChartMaxX = m_pPosHelper->getLogicMaxX();
1357 double fMinX = fChartMinX;
1358 double fMaxX = fChartMaxX;
1360 double fPointScale = 1.0;
1362 if( !bAverageLine )
1364 rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
1365 fMaxX += aExtrapolateForward;
1366 fMinX -= aExtrapolateBackward;
1368 fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
1369 // sanitize the value, tdf#119922
1370 fPointScale = std::min(fPointScale, 1000.0);
1373 xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod);
1374 xCalculator->recalculateRegression( rVDataSeries.getAllX(), rVDataSeries.getAllY() );
1375 sal_Int32 nPointCount = 100 * fPointScale;
1377 if ( nPointCount < 2 )
1378 nPointCount = 2;
1380 std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
1381 uno::Reference< chart2::XScaling > xScalingX;
1382 uno::Reference< chart2::XScaling > xScalingY;
1383 if( aScales.size() >= 2 )
1385 xScalingX.set( aScales[0].Scaling );
1386 xScalingY.set( aScales[1].Scaling );
1389 const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
1390 xCalculator->getCurveValues(
1391 fMinX, fMaxX, nPointCount,
1392 xScalingX, xScalingY, bMaySkipPoints ));
1394 nPointCount = aCalculatedPoints.getLength();
1396 drawing::PolyPolygonShape3D aRegressionPoly;
1397 aRegressionPoly.SequenceX.realloc(1);
1398 aRegressionPoly.SequenceY.realloc(1);
1399 aRegressionPoly.SequenceZ.realloc(1);
1400 aRegressionPoly.SequenceX[0].realloc(nPointCount);
1401 aRegressionPoly.SequenceY[0].realloc(nPointCount);
1402 aRegressionPoly.SequenceZ[0].realloc(nPointCount);
1404 sal_Int32 nRealPointCount = 0;
1406 for(geometry::RealPoint2D const & p : aCalculatedPoints)
1408 double fLogicX = p.X;
1409 double fLogicY = p.Y;
1410 double fLogicZ = 0.0; //dummy
1412 // fdo#51656: don't scale mean value lines
1413 if(!bAverageLine)
1414 m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
1416 if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
1417 !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
1418 !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
1420 aRegressionPoly.SequenceX[0][nRealPointCount] = fLogicX;
1421 aRegressionPoly.SequenceY[0][nRealPointCount] = fLogicY;
1422 nRealPointCount++;
1425 aRegressionPoly.SequenceX[0].realloc(nRealPointCount);
1426 aRegressionPoly.SequenceY[0].realloc(nRealPointCount);
1427 aRegressionPoly.SequenceZ[0].realloc(nRealPointCount);
1429 drawing::PolyPolygonShape3D aClippedPoly;
1430 Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
1431 aRegressionPoly = aClippedPoly;
1432 m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
1434 awt::Point aDefaultPos;
1435 if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
1437 VLineProperties aVLineProperties;
1438 aVLineProperties.initFromPropertySet( xProperties );
1440 //create an extra group shape for each curve for selection handling
1441 uno::Reference< drawing::XShapes > xRegressionGroupShapes =
1442 createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
1443 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1444 xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
1445 ShapeFactory::setShapeName( xShape, "MarkHandles" );
1446 aDefaultPos = xShape->getPosition();
1449 // curve equation and correlation coefficient
1450 uno::Reference< beans::XPropertySet > xEquationProperties( aCurveList[nN]->getEquationProperties());
1451 if( xEquationProperties.is())
1453 createRegressionCurveEquationShapes(
1454 rVDataSeries.getDataCurveEquationCID( nN ),
1455 xEquationProperties, xEquationTarget, xCalculator,
1456 aDefaultPos );
1461 static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
1463 const sal_Int32 nStringLength = aString.getLength();
1464 sal_Int32 nMaxLineLength = 0;
1466 for ( sal_Int32 i=0; i<nStringLength; i++ )
1468 sal_Int32 indexSep = aString.indexOf( "\n", i );
1469 if ( indexSep < 0 )
1470 indexSep = nStringLength;
1471 sal_Int32 nLineLength = indexSep - i;
1472 if ( nLineLength > nMaxLineLength )
1473 nMaxLineLength = nLineLength;
1474 i = indexSep;
1477 return nMaxLineLength;
1480 void VSeriesPlotter::createRegressionCurveEquationShapes(
1481 const OUString & rEquationCID,
1482 const uno::Reference< beans::XPropertySet > & xEquationProperties,
1483 const uno::Reference< drawing::XShapes >& xEquationTarget,
1484 const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
1485 awt::Point aDefaultPos )
1487 OSL_ASSERT( xEquationProperties.is());
1488 if( !xEquationProperties.is())
1489 return;
1491 bool bShowEquation = false;
1492 bool bShowCorrCoeff = false;
1493 if(!(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
1494 ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff )))
1495 return;
1497 if( ! (bShowEquation || bShowCorrCoeff))
1498 return;
1500 OUStringBuffer aFormula;
1501 sal_Int32 nNumberFormatKey = 0;
1502 sal_Int32 nFormulaWidth = 0;
1503 xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
1504 bool bResizeEquation = true;
1505 sal_Int32 nMaxIteration = 2;
1506 if ( bShowEquation )
1508 OUString aXName, aYName;
1509 if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
1510 aXName = OUString( "x" );
1511 if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
1512 aYName = OUString( "f(x)" );
1513 xRegressionCurveCalculator->setXYNames( aXName, aYName );
1516 for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
1518 bResizeEquation = false;
1519 if( bShowEquation )
1521 if (m_apNumberFormatterWrapper)
1522 { // iteration 0: default representation (no wrap)
1523 // iteration 1: expected width (nFormulaWidth) is calculated
1524 aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
1525 m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
1526 nNumberFormatKey, nFormulaWidth );
1527 nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
1529 else
1531 aFormula = xRegressionCurveCalculator->getRepresentation();
1534 if( bShowCorrCoeff )
1536 aFormula.append( "\n" );
1539 if( bShowCorrCoeff )
1541 aFormula.append( "R" ).append( OUStringChar( aSuperscriptFigures[2] ) ).append( " = " );
1542 double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
1543 if (m_apNumberFormatterWrapper)
1545 Color nLabelCol;
1546 bool bColChanged;
1547 aFormula.append(
1548 m_apNumberFormatterWrapper->getFormattedString(
1549 nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
1550 //@todo: change color of label if bColChanged is true
1552 else
1554 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
1555 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
1556 sal_Unicode aDecimalSep = aNumDecimalSep[0];
1557 aFormula.append( ::rtl::math::doubleToUString(
1558 fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
1562 awt::Point aScreenPosition2D;
1563 chart2::RelativePosition aRelativePosition;
1564 if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
1566 //@todo decide whether x is primary or secondary
1567 double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
1568 double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
1569 aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
1570 aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
1572 else
1573 aScreenPosition2D = aDefaultPos;
1575 if( !aFormula.isEmpty())
1577 // set fill and line properties on creation
1578 tNameSequence aNames;
1579 tAnySequence aValues;
1580 PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
1582 uno::Reference< drawing::XShape > xTextShape = m_pShapeFactory->createText(
1583 xEquationTarget, aFormula.makeStringAndClear(),
1584 aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
1586 OSL_ASSERT( xTextShape.is());
1587 if( xTextShape.is())
1589 ShapeFactory::setShapeName( xTextShape, rEquationCID );
1590 awt::Size aSize( xTextShape->getSize() );
1591 awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
1592 aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
1593 //ensure that the equation is fully placed within the page (if possible)
1594 if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
1595 aPos.X = m_aPageReferenceSize.Width - aSize.Width;
1596 if( aPos.X < 0 )
1598 aPos.X = 0;
1599 if ( nFormulaWidth > 0 )
1601 bResizeEquation = true;
1602 if ( nCountIteration < nMaxIteration-1 )
1603 xEquationTarget->remove( xTextShape ); // remove equation
1604 nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
1605 nFormulaWidth -= nCountIteration;
1606 if ( nFormulaWidth < 0 )
1607 nFormulaWidth = 0;
1610 if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
1611 aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
1612 if( aPos.Y < 0 )
1613 aPos.Y = 0;
1614 if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
1615 xTextShape->setPosition(aPos); // if equation was not removed
1621 void VSeriesPlotter::setMappedProperties(
1622 const uno::Reference< drawing::XShape >& xTargetShape
1623 , const uno::Reference< beans::XPropertySet >& xSource
1624 , const tPropertyNameMap& rMap
1625 , tPropertyNameValueMap const * pOverwriteMap )
1627 uno::Reference< beans::XPropertySet > xTargetProp( xTargetShape, uno::UNO_QUERY );
1628 PropertyMapper::setMappedProperties(xTargetProp,xSource,rMap,pOverwriteMap);
1631 void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
1633 m_nTimeResolution = TimeResolution;
1634 m_aNullDate = rNullDate;
1637 // MinimumAndMaximumSupplier
1638 tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
1640 tools::Long nRet = css::chart::TimeUnit::YEAR;
1641 if (!m_pExplicitCategoriesProvider)
1642 return nRet;
1644 const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
1645 if (rDateCategories.empty())
1646 return nRet;
1648 std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
1650 aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
1651 if (aIt == aEnd)
1652 return nRet;
1654 Date aNullDate(30,12,1899);
1655 if (m_apNumberFormatterWrapper)
1656 aNullDate = m_apNumberFormatterWrapper->getNullDate();
1658 Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
1659 ++aIt;
1660 for(;aIt!=aEnd;++aIt)
1662 if (std::isnan(*aIt))
1663 continue;
1665 Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
1666 if( nRet == css::chart::TimeUnit::YEAR )
1668 if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
1669 nRet = css::chart::TimeUnit::MONTH;
1671 if( nRet == css::chart::TimeUnit::MONTH )
1673 if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
1674 nRet = css::chart::TimeUnit::DAY;
1676 if( nRet == css::chart::TimeUnit::DAY )
1677 break;
1678 aPrevious=aCurrent;
1681 return nRet;
1683 double VSeriesPlotter::getMinimumX()
1685 double fMinimum, fMaximum;
1686 getMinimumAndMaximumX( fMinimum, fMaximum );
1687 return fMinimum;
1689 double VSeriesPlotter::getMaximumX()
1691 double fMinimum, fMaximum;
1692 getMinimumAndMaximumX( fMinimum, fMaximum );
1693 return fMaximum;
1696 double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1698 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1700 double fMinY, fMaxY;
1701 getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1702 return fMinY;
1705 double fMinimum, fMaximum;
1706 ::rtl::math::setInf(&fMinimum, false);
1707 ::rtl::math::setInf(&fMaximum, true);
1708 for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
1710 for(VDataSeriesGroup & rXSlot : rXSlots)
1712 double fLocalMinimum, fLocalMaximum;
1713 rXSlot.calculateYMinAndMaxForCategoryRange(
1714 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1715 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1716 , isSeparateStackingForDifferentSigns( 1 )
1717 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1718 if(fMaximum<fLocalMaximum)
1719 fMaximum=fLocalMaximum;
1720 if(fMinimum>fLocalMinimum)
1721 fMinimum=fLocalMinimum;
1724 if(std::isinf(fMinimum))
1725 ::rtl::math::setNan(&fMinimum);
1726 return fMinimum;
1729 double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1731 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1733 double fMinY, fMaxY;
1734 getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1735 return fMaxY;
1738 double fMinimum, fMaximum;
1739 ::rtl::math::setInf(&fMinimum, false);
1740 ::rtl::math::setInf(&fMaximum, true);
1741 for( std::vector< VDataSeriesGroup > & rXSlots : m_aZSlots)
1743 for(VDataSeriesGroup & rXSlot : rXSlots)
1745 double fLocalMinimum, fLocalMaximum;
1746 rXSlot.calculateYMinAndMaxForCategoryRange(
1747 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1748 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1749 , isSeparateStackingForDifferentSigns( 1 )
1750 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1751 if(fMaximum<fLocalMaximum)
1752 fMaximum=fLocalMaximum;
1753 if(fMinimum>fLocalMinimum)
1754 fMinimum=fLocalMinimum;
1757 if(std::isinf(fMaximum))
1758 ::rtl::math::setNan(&fMaximum);
1759 return fMaximum;
1762 double VSeriesPlotter::getMinimumZ()
1764 //this is the default for all charts without a meaningful z axis
1765 return 1.0;
1767 double VSeriesPlotter::getMaximumZ()
1769 if( m_nDimension!=3 || m_aZSlots.empty() )
1770 return getMinimumZ()+1;
1771 return m_aZSlots.size();
1774 namespace
1776 bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
1778 // default implementation: true for Y axes, and for value X axis
1779 if( nDimensionIndex == 0 )
1780 return !bCategoryXAxis;
1781 return nDimensionIndex == 1;
1785 bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
1787 return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1790 bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
1792 // do not expand axes in 3D charts
1793 return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1796 bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
1798 // default implementation: only for Y axis
1799 return nDimensionIndex == 1;
1802 bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
1804 // default implementation: only for Y axis
1805 return nDimensionIndex == 1;
1808 bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
1810 // default implementation: only for Y axis
1811 return nDimensionIndex == 1;
1814 void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1816 ::rtl::math::setInf(&rfMinimum, false);
1817 ::rtl::math::setInf(&rfMaximum, true);
1819 for (auto const& ZSlot : m_aZSlots)
1821 for (auto const& XSlot : ZSlot)
1823 double fLocalMinimum, fLocalMaximum;
1824 XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
1825 if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
1826 rfMinimum = fLocalMinimum;
1827 if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
1828 rfMaximum = fLocalMaximum;
1831 if(std::isinf(rfMinimum))
1832 ::rtl::math::setNan(&rfMinimum);
1833 if(std::isinf(rfMaximum))
1834 ::rtl::math::setNan(&rfMaximum);
1837 void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
1839 ::rtl::math::setInf(&rfMinY, false);
1840 ::rtl::math::setInf(&rfMaxY, true);
1842 for (auto const& ZSlot : m_aZSlots)
1844 for (auto const& XSlot : ZSlot)
1846 double fLocalMinimum, fLocalMaximum;
1847 XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
1848 if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
1849 rfMinY = fLocalMinimum;
1850 if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
1851 rfMaxY = fLocalMaximum;
1854 if(std::isinf(rfMinY))
1855 ::rtl::math::setNan(&rfMinY);
1856 if(std::isinf(rfMaxY))
1857 ::rtl::math::setNan(&rfMaxY);
1860 sal_Int32 VSeriesPlotter::getPointCount() const
1862 sal_Int32 nRet = 0;
1864 for (auto const& ZSlot : m_aZSlots)
1866 for (auto const& XSlot : ZSlot)
1868 sal_Int32 nPointCount = XSlot.getPointCount();
1869 if( nPointCount>nRet )
1870 nRet = nPointCount;
1873 return nRet;
1876 void VSeriesPlotter::setNumberFormatsSupplier(
1877 const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
1879 m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
1882 void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
1884 m_xColorScheme = xColorScheme;
1887 void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
1889 m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
1892 sal_Int32 VDataSeriesGroup::getPointCount() const
1894 if(!m_bMaxPointCountDirty)
1895 return m_nMaxPointCount;
1897 sal_Int32 nRet = 0;
1899 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1901 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1902 if( nPointCount>nRet )
1903 nRet = nPointCount;
1905 m_nMaxPointCount=nRet;
1906 m_aListOfCachedYValues.clear();
1907 m_aListOfCachedYValues.resize(m_nMaxPointCount);
1908 m_bMaxPointCountDirty=false;
1909 return nRet;
1912 sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1914 sal_Int32 nRet = 0;
1916 if (!m_aSeriesVector.empty())
1917 nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
1919 return nRet;
1922 void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1925 ::rtl::math::setInf(&rfMinimum, false);
1926 ::rtl::math::setInf(&rfMaximum, true);
1928 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1930 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1931 for(sal_Int32 nN=0;nN<nPointCount;nN++)
1933 double fX = pSeries->getXValue( nN );
1934 if( std::isnan(fX) )
1935 continue;
1936 if(rfMaximum<fX)
1937 rfMaximum=fX;
1938 if(rfMinimum>fX)
1939 rfMinimum=fX;
1942 if(std::isinf(rfMinimum))
1943 ::rtl::math::setNan(&rfMinimum);
1944 if(std::isinf(rfMaximum))
1945 ::rtl::math::setNan(&rfMaximum);
1948 namespace {
1951 * Keep track of minimum and maximum Y values for one or more data series.
1952 * When multiple data series exist, that indicates that the data series are
1953 * stacked.
1955 * <p>For each X value, we calculate separate Y value ranges for each data
1956 * series in the first pass. In the second pass, we calculate the minimum Y
1957 * value by taking the absolute minimum value of all data series, whereas
1958 * the maximum Y value is the sum of all the series maximum Y values.</p>
1960 * <p>Once that's done for all X values, the final min / max Y values get
1961 * calculated by taking the absolute min / max Y values across all the X
1962 * values.</p>
1964 class PerXMinMaxCalculator
1966 typedef std::pair<double, double> MinMaxType;
1967 typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
1968 typedef std::map<double, std::unique_ptr<SeriesMinMaxType>> GroupMinMaxType;
1969 typedef std::unordered_map<double, MinMaxType> TotalStoreType;
1970 GroupMinMaxType m_SeriesGroup;
1971 size_t mnCurSeries;
1973 public:
1974 PerXMinMaxCalculator() : mnCurSeries(0) {}
1976 void nextSeries() { ++mnCurSeries; }
1978 void setValue(double fX, double fY)
1980 SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
1981 if (!pStore)
1982 // This shouldn't happen!
1983 return;
1985 SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
1986 if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
1988 MinMaxType& r = it->second;
1989 // A min-max pair already exists for this series. Update it.
1990 if (fY < r.first)
1991 r.first = fY;
1992 if (r.second < fY)
1993 r.second = fY;
1995 else
1997 // No existing pair. Insert a new one.
1998 pStore->insert(
1999 it, SeriesMinMaxType::value_type(
2000 mnCurSeries, MinMaxType(fY,fY)));
2004 void getTotalRange(double& rfMin, double& rfMax) const
2006 rtl::math::setNan(&rfMin);
2007 rtl::math::setNan(&rfMax);
2009 TotalStoreType aStore;
2010 getTotalStore(aStore);
2012 if (aStore.empty())
2013 return;
2015 TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
2016 rfMin = it->second.first;
2017 rfMax = it->second.second;
2018 for (++it; it != itEnd; ++it)
2020 if (rfMin > it->second.first)
2021 rfMin = it->second.first;
2022 if (rfMax < it->second.second)
2023 rfMax = it->second.second;
2027 private:
2029 * Parse all data and reduce them into a set of global Y value ranges per
2030 * X value.
2032 void getTotalStore(TotalStoreType& rStore) const
2034 TotalStoreType aStore;
2035 for (auto const& it : m_SeriesGroup)
2037 double fX = it.first;
2039 const SeriesMinMaxType& rSeries = *it.second;
2040 for (auto const& series : rSeries)
2042 double fYMin = series.second.first, fYMax = series.second.second;
2043 TotalStoreType::iterator itr = aStore.find(fX);
2044 if (itr == aStore.end())
2045 // New min-max pair for give X value.
2046 aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
2047 else
2049 MinMaxType& r = itr->second;
2050 if (fYMin < r.first)
2051 r.first = fYMin; // min y-value
2053 r.second += fYMax; // accumulative max y-value.
2057 rStore.swap(aStore);
2060 SeriesMinMaxType* getByXValue(double fX)
2062 GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
2063 if (it == m_SeriesGroup.end())
2065 std::pair<GroupMinMaxType::iterator,bool> r =
2066 m_SeriesGroup.insert(std::make_pair(fX, std::make_unique<SeriesMinMaxType>()));
2068 if (!r.second)
2069 // insertion failed.
2070 return nullptr;
2072 it = r.first;
2075 return it->second.get();
2081 void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
2082 double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
2084 ::rtl::math::setNan(&rfMinY);
2085 ::rtl::math::setNan(&rfMaxY);
2087 if (m_aSeriesVector.empty())
2088 // No data series. Bail out.
2089 return;
2091 PerXMinMaxCalculator aRangeCalc;
2092 for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
2094 if (!pSeries)
2095 continue;
2097 for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
2099 if (nAxisIndex != pSeries->getAttachedAxisIndex())
2100 continue;
2102 double fX = pSeries->getXValue(i);
2103 if (std::isnan(fX))
2104 continue;
2106 if (fX < fMinX || fX > fMaxX)
2107 // Outside specified X range. Skip it.
2108 continue;
2110 double fY = pSeries->getYValue(i);
2111 if (std::isnan(fY))
2112 continue;
2114 aRangeCalc.setValue(fX, fY);
2116 aRangeCalc.nextSeries();
2119 aRangeCalc.getTotalRange(rfMinY, rfMaxY);
2122 void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
2123 , bool bSeparateStackingForDifferentSigns
2124 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2126 assert(nCategoryIndex >= 0);
2127 assert(nCategoryIndex < getPointCount());
2129 ::rtl::math::setInf(&rfMinimumY, false);
2130 ::rtl::math::setInf(&rfMaximumY, true);
2132 if(m_aSeriesVector.empty())
2133 return;
2135 CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
2136 if( !aCachedYValues.m_bValuesDirty )
2138 //return cached values
2139 rfMinimumY = aCachedYValues.m_fMinimumY;
2140 rfMaximumY = aCachedYValues.m_fMaximumY;
2141 return;
2144 double fTotalSum, fPositiveSum, fNegativeSum, fFirstPositiveY, fFirstNegativeY;
2145 ::rtl::math::setNan( &fTotalSum );
2146 ::rtl::math::setNan( &fPositiveSum );
2147 ::rtl::math::setNan( &fNegativeSum );
2148 ::rtl::math::setNan( &fFirstPositiveY );
2149 ::rtl::math::setNan( &fFirstNegativeY );
2151 if( bSeparateStackingForDifferentSigns )
2153 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2155 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2156 continue;
2158 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2159 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2161 if( fValueMaxY >= 0 )
2163 if( std::isnan( fPositiveSum ) )
2164 fPositiveSum = fFirstPositiveY = fValueMaxY;
2165 else
2166 fPositiveSum += fValueMaxY;
2168 if( fValueMinY < 0 )
2170 if(std::isnan( fNegativeSum ))
2171 fNegativeSum = fFirstNegativeY = fValueMinY;
2172 else
2173 fNegativeSum += fValueMinY;
2176 rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
2177 rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
2179 else
2181 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2183 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2184 continue;
2186 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2187 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2189 if( std::isnan( fTotalSum ) )
2191 rfMinimumY = fValueMinY;
2192 rfMaximumY = fTotalSum = fValueMaxY;
2194 else
2196 fTotalSum += fValueMaxY;
2197 if( rfMinimumY > fTotalSum )
2198 rfMinimumY = fTotalSum;
2199 if( rfMaximumY < fTotalSum )
2200 rfMaximumY = fTotalSum;
2205 aCachedYValues.m_fMinimumY = rfMinimumY;
2206 aCachedYValues.m_fMaximumY = rfMaximumY;
2207 aCachedYValues.m_bValuesDirty = false;
2208 m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
2211 void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
2212 sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
2213 , bool bSeparateStackingForDifferentSigns
2214 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2216 //@todo maybe cache these values
2217 ::rtl::math::setInf(&rfMinimumY, false);
2218 ::rtl::math::setInf(&rfMaximumY, true);
2220 //iterate through the given categories
2221 if(nStartCategoryIndex<0)
2222 nStartCategoryIndex=0;
2223 const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
2224 if(nPointCount <= 0)
2225 return;
2226 if (nEndCategoryIndex >= nPointCount)
2227 nEndCategoryIndex = nPointCount - 1;
2228 if(nEndCategoryIndex<0)
2229 nEndCategoryIndex=0;
2230 for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
2232 double fMinimumY; ::rtl::math::setNan(&fMinimumY);
2233 double fMaximumY; ::rtl::math::setNan(&fMaximumY);
2235 calculateYMinAndMaxForCategory( nCatIndex
2236 , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
2238 if(rfMinimumY > fMinimumY)
2239 rfMinimumY = fMinimumY;
2240 if(rfMaximumY < fMaximumY)
2241 rfMaximumY = fMaximumY;
2245 double VSeriesPlotter::getTransformedDepth() const
2247 double MinZ = m_pMainPosHelper->getLogicMinZ();
2248 double MaxZ = m_pMainPosHelper->getLogicMaxZ();
2249 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
2250 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
2251 return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
2254 void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
2256 if( nAxisIndex<1 )
2257 return;
2259 m_aSecondaryValueScales[nAxisIndex]=rScale;
2262 PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
2264 PlottingPositionHelper* pRet = nullptr;
2265 if(nAxisIndex>0)
2267 tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
2268 if( aPosIt != m_aSecondaryPosHelperMap.end() )
2270 pRet = aPosIt->second.get();
2272 else if (m_pPosHelper)
2274 tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
2275 if( aScaleIt != m_aSecondaryValueScales.end() )
2277 m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
2278 pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
2282 if( !pRet )
2283 pRet = m_pMainPosHelper;
2284 if(pRet)
2285 pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
2286 return *pRet;
2289 void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
2293 VDataSeries* VSeriesPlotter::getFirstSeries() const
2295 for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
2297 if (!rGroup.empty())
2299 if (!rGroup[0].m_aSeriesVector.empty())
2301 VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
2302 if (pSeries)
2303 return pSeries;
2307 return nullptr;
2310 OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
2312 if (m_pExplicitCategoriesProvider)
2314 Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
2315 if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
2317 return aCategories[nPointIndex];
2320 return OUString();
2323 uno::Sequence< OUString > VSeriesPlotter::getSeriesNames() const
2325 std::vector<OUString> aRetVector;
2327 OUString aRole;
2328 if( m_xChartTypeModel.is() )
2329 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2331 for (auto const& rGroup : m_aZSlots)
2333 if (!rGroup.empty())
2335 VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
2336 if (!rSeriesGroup.m_aSeriesVector.empty())
2338 VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
2339 uno::Reference< XDataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
2340 if( xSeries.is() )
2342 OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
2343 aRetVector.push_back( aSeriesName );
2348 return comphelper::containerToSequence( aRetVector );
2351 void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
2353 m_aPageReferenceSize = rPageRefSize;
2355 // set reference size also at all data series
2357 for (auto const & outer : m_aZSlots)
2358 for (VDataSeriesGroup const & rGroup : outer)
2360 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2362 pSeries->setPageReferenceSize(m_aPageReferenceSize);
2367 //better performance for big data
2368 void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
2370 m_aCoordinateSystemResolution = rCoordinateSystemResolution;
2373 bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
2375 return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
2378 bool VSeriesPlotter::shouldSnapRectToUsedArea()
2380 return m_nDimension != 3;
2383 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
2384 const awt::Size& rEntryKeyAspectRatio
2385 , LegendPosition eLegendPosition
2386 , const Reference< beans::XPropertySet >& xTextProperties
2387 , const Reference< drawing::XShapes >& xTarget
2388 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2389 , const Reference< uno::XComponentContext >& xContext
2390 , ChartModel& rModel
2393 std::vector< ViewLegendEntry > aResult;
2395 if( xTarget.is() )
2397 uno::Reference< XCoordinateSystemContainer > xCooSysCnt( rModel.getFirstDiagram(), uno::UNO_QUERY );
2398 Reference< chart2::XCoordinateSystem > xCooSys(xCooSysCnt->getCoordinateSystems()[0]);
2399 Reference< beans::XPropertySet > xProp( xCooSys, uno::UNO_QUERY );
2400 bool bSwapXAndY = false;
2402 if( xProp.is()) try
2404 xProp->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
2406 catch( const uno::Exception& )
2410 //iterate through all series
2411 bool bBreak = false;
2412 bool bFirstSeries = true;
2415 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
2417 for (VDataSeriesGroup const & rGroup : rGroupVector)
2419 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2421 if (!pSeries)
2422 continue;
2424 if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
2426 continue;
2429 std::vector<ViewLegendEntry> aSeriesEntries(
2430 createLegendEntriesForSeries(
2431 rEntryKeyAspectRatio, *pSeries, xTextProperties,
2432 xTarget, xShapeFactory, xContext));
2434 //add series entries to the result now
2436 // use only the first series if VaryColorsByPoint is set for the first series
2437 if (bFirstSeries && pSeries->isVaryColorsByPoint())
2438 bBreak = true;
2439 bFirstSeries = false;
2441 // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
2442 // If the legend is top or bottom and we have a stacked bar-chart the normal order
2443 // is the correct one, unless the chart type is horizontal bar-chart.
2444 bool bReverse = false;
2445 if ( bSwapXAndY )
2447 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2448 bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
2450 else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
2452 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2453 bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
2456 if (bReverse)
2457 aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
2458 else
2459 aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
2461 if (bBreak)
2462 return aResult;
2467 return aResult;
2470 std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
2472 std::vector<VDataSeries*> aAllSeries;
2473 for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
2475 for(VDataSeriesGroup const & rGroup : rXSlot)
2477 for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
2478 aAllSeries.push_back(p.get());
2481 return aAllSeries;
2484 namespace
2486 bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
2488 bool bHasVisibleLine = false;
2489 rbHasDashedLine = false;
2490 drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
2491 if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
2493 if( aLineStyle != drawing::LineStyle_NONE )
2494 bHasVisibleLine = true;
2495 if( aLineStyle == drawing::LineStyle_DASH )
2496 rbHasDashedLine = true;
2498 return bHasVisibleLine;
2501 bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
2503 bool bHasRegressionCurves = false;
2504 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2505 if( xRegrCont.is())
2507 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves() );
2508 sal_Int32 i = 0, nCount = aCurves.getLength();
2509 for( i=0; i<nCount; ++i )
2511 if( aCurves[i].is() )
2513 bHasRegressionCurves = true;
2514 lcl_HasVisibleLine( uno::Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ), rbHasDashedLine );
2518 return bHasRegressionCurves;
2521 LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
2523 return LegendSymbolStyle::Box;
2526 awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
2528 awt::Size aRet(1000,1000);
2529 if( m_nDimension==3 )
2530 return aRet;
2532 bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
2533 bool bHasLines = false;
2534 bool bHasDashedLines = false;
2535 //iterate through all series
2536 for (VDataSeries* pSeries : getAllSeries())
2538 if( bSeriesAllowsLines )
2540 bool bCurrentDashed = false;
2541 if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
2543 bHasLines = true;
2544 if( bCurrentDashed )
2546 bHasDashedLines = true;
2547 break;
2551 bool bRegressionHasDashedLines=false;
2552 if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
2554 bHasLines = true;
2555 if( bRegressionHasDashedLines )
2557 bHasDashedLines = true;
2558 break;
2562 if( bHasLines )
2564 if( bHasDashedLines )
2565 aRet = awt::Size(1600,-1);
2566 else
2567 aRet = awt::Size(800,-1);
2569 return aRet;
2572 uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
2574 return uno::Any();
2577 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForSeries(
2578 const awt::Size& rEntryKeyAspectRatio
2579 , const VDataSeries& rSeries
2580 , const Reference< drawing::XShapes >& xTarget
2581 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2584 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2585 uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
2587 VLegendSymbolFactory::PropertyType ePropType =
2588 VLegendSymbolFactory::PropertyType::FilledSeries;
2590 // todo: maybe the property-style does not solely depend on the
2591 // legend-symbol type
2592 switch( eLegendSymbolStyle )
2594 case LegendSymbolStyle::Line:
2595 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2596 break;
2597 default:
2598 break;
2600 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2601 xTarget, eLegendSymbolStyle, xShapeFactory
2602 , rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol ));
2604 return xShape;
2607 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForPoint(
2608 const awt::Size& rEntryKeyAspectRatio
2609 , const VDataSeries& rSeries
2610 , sal_Int32 nPointIndex
2611 , const Reference< drawing::XShapes >& xTarget
2612 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2615 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2616 uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
2618 VLegendSymbolFactory::PropertyType ePropType =
2619 VLegendSymbolFactory::PropertyType::FilledSeries;
2621 // todo: maybe the property-style does not solely depend on the
2622 // legend-symbol type
2623 switch( eLegendSymbolStyle )
2625 case LegendSymbolStyle::Line:
2626 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2627 break;
2628 default:
2629 break;
2632 // the default properties for the data point are the data series properties.
2633 // If a data point has own attributes overwrite them
2634 Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
2635 Reference< beans::XPropertySet > xPointSet( xSeriesProps );
2636 if( rSeries.isAttributedDataPoint( nPointIndex ) )
2637 xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
2639 // if a data point has no own color use a color from the diagram's color scheme
2640 if( ! rSeries.hasPointOwnColor( nPointIndex ))
2642 Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
2643 if( xCloneable.is() && m_xColorScheme.is() )
2645 xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
2646 Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
2647 if( xChild.is())
2648 xChild->setParent( xSeriesProps );
2650 OSL_ASSERT( xPointSet.is());
2651 xPointSet->setPropertyValue(
2652 "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
2656 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2657 xTarget, eLegendSymbolStyle, xShapeFactory, xPointSet, ePropType, aExplicitSymbol ));
2659 return xShape;
2662 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
2663 const awt::Size& rEntryKeyAspectRatio
2664 , const VDataSeries& rSeries
2665 , const Reference< beans::XPropertySet >& xTextProperties
2666 , const Reference< drawing::XShapes >& xTarget
2667 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2668 , const Reference< uno::XComponentContext >& xContext
2671 std::vector< ViewLegendEntry > aResult;
2673 if( ! ( xShapeFactory.is() && xTarget.is() && xContext.is() ) )
2674 return aResult;
2678 ViewLegendEntry aEntry;
2679 OUString aLabelText;
2680 bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
2681 bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
2682 CHART2_SERVICE_NAME_CHARTTYPE_PIE);
2685 if (bIsPie && m_xChartTypeModelProps.is())
2687 bool bDonut = false;
2688 if ((m_xChartTypeModelProps->getPropertyValue("UseRings") >>= bDonut) && bDonut)
2689 bIsPie = false;
2692 catch (const uno::Exception&)
2696 if (bVaryColorsByPoint || bIsPie)
2698 Sequence< OUString > aCategoryNames;
2699 if( m_pExplicitCategoriesProvider )
2700 aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
2701 Sequence<sal_Int32> deletedLegendEntries;
2704 rSeries.getPropertiesOfSeries()->getPropertyValue("DeletedLegendEntries") >>= deletedLegendEntries;
2706 catch (const uno::Exception&)
2709 for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
2711 bool deletedLegendEntry = false;
2712 for (auto& deletedLegendEntryIdx : deletedLegendEntries)
2714 if (nIdx == deletedLegendEntryIdx)
2716 deletedLegendEntry = true;
2717 break;
2720 if (deletedLegendEntry)
2721 continue;
2723 // symbol
2724 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2726 // create the symbol
2727 Reference< drawing::XShape > xShape( createLegendSymbolForPoint( rEntryKeyAspectRatio,
2728 rSeries, nIdx, xSymbolGroup, xShapeFactory ) );
2730 // set CID to symbol for selection
2731 if( xShape.is() )
2733 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2735 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
2736 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2737 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2738 ShapeFactory::setShapeName( xShape, aCID );
2741 // label
2742 aLabelText = aCategoryNames[nIdx];
2743 if( xShape.is() || !aLabelText.isEmpty() )
2745 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2746 aResult.push_back(aEntry);
2750 else
2752 // symbol
2753 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2755 // create the symbol
2756 Reference< drawing::XShape > xShape( createLegendSymbolForSeries(
2757 rEntryKeyAspectRatio, rSeries, xSymbolGroup, xShapeFactory ) );
2759 // set CID to symbol for selection
2760 if( xShape.is())
2762 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2764 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2765 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2766 ShapeFactory::setShapeName( xShape, aCID );
2769 // label
2770 aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
2771 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2773 aResult.push_back(aEntry);
2776 // don't show legend entry of regression curve & friends if this type of chart
2777 // doesn't support statistics #i63016#, fdo#37197
2778 if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
2779 return aResult;
2781 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2782 if( xRegrCont.is())
2784 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves());
2785 sal_Int32 i = 0, nCount = aCurves.getLength();
2786 for( i=0; i<nCount; ++i )
2788 if( aCurves[i].is() )
2790 //label
2791 OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
2792 replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
2793 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
2795 // symbol
2796 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2798 // create the symbol
2799 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2800 xSymbolGroup, LegendSymbolStyle::Line, xShapeFactory,
2801 Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ),
2802 VLegendSymbolFactory::PropertyType::Line, uno::Any() ));
2804 // set CID to symbol for selection
2805 if( xShape.is())
2807 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2809 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
2810 ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
2811 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
2812 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2813 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2814 ShapeFactory::setShapeName( xShape, aCID );
2817 aResult.push_back(aEntry);
2822 catch( const uno::Exception & )
2824 DBG_UNHANDLED_EXCEPTION("chart2" );
2826 return aResult;
2829 VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
2830 const uno::Reference<XChartType>& xChartTypeModel
2831 , sal_Int32 nDimensionCount
2832 , bool bExcludingPositioning )
2834 if (!xChartTypeModel.is())
2835 return nullptr;
2837 OUString aChartType = xChartTypeModel->getChartType();
2839 VSeriesPlotter* pRet=nullptr;
2840 if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
2841 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2842 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
2843 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2844 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
2845 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
2846 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
2847 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
2848 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
2849 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2850 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
2851 pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
2852 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
2853 pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
2854 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
2855 pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
2856 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
2857 pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
2858 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
2859 pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
2860 else
2861 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2862 return pRet;
2865 } //namespace chart
2867 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */