Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / chart2 / source / view / charttypes / VSeriesPlotter.cxx
blob33424056d72039723b787f9ffedce93d27692b2e
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 <VLineProperties.hxx>
23 #include <ShapeFactory.hxx>
25 #include <CommonConverters.hxx>
26 #include <ExplicitCategoriesProvider.hxx>
27 #include <ObjectIdentifier.hxx>
28 #include <StatisticsHelper.hxx>
29 #include <PlottingPositionHelper.hxx>
30 #include <LabelPositionHelper.hxx>
31 #include <ChartTypeHelper.hxx>
32 #include <Clipping.hxx>
33 #include <servicenames_charttypes.hxx>
34 #include <NumberFormatterWrapper.hxx>
35 #include <DataSeriesHelper.hxx>
36 #include <RegressionCurveHelper.hxx>
37 #include <VLegendSymbolFactory.hxx>
38 #include <FormattedStringHelper.hxx>
39 #include <RelativePositionHelper.hxx>
40 #include <DateHelper.hxx>
41 #include <DiagramHelper.hxx>
42 #include <defines.hxx>
43 #include <ChartModel.hxx>
45 //only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
46 #include "BarChart.hxx"
47 #include "PieChart.hxx"
48 #include "AreaChart.hxx"
49 #include "CandleStickChart.hxx"
50 #include "BubbleChart.hxx"
51 #include "NetChart.hxx"
52 #include <unonames.hxx>
53 #include <SpecialCharacters.hxx>
55 #include <com/sun/star/chart2/DataPointLabel.hpp>
56 #include <com/sun/star/chart/ErrorBarStyle.hpp>
57 #include <com/sun/star/chart/TimeUnit.hpp>
58 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
59 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
60 #include <com/sun/star/container/XChild.hpp>
61 #include <com/sun/star/chart2/RelativePosition.hpp>
62 #include <editeng/unoprnms.hxx>
63 #include <tools/color.hxx>
64 #include <rtl/ustrbuf.hxx>
65 #include <rtl/math.hxx>
66 #include <basegfx/vector/b2dvector.hxx>
67 #include <com/sun/star/drawing/LineStyle.hpp>
68 #include <com/sun/star/util/XCloneable.hpp>
69 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
71 #include <com/sun/star/style/ParagraphAdjust.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::chart2;
90 using ::com::sun::star::uno::Reference;
91 using ::com::sun::star::uno::Sequence;
93 VDataSeriesGroup::CachedYValues::CachedYValues()
94 : m_bValuesDirty(true)
95 , m_fMinimumY(0.0)
96 , m_fMaximumY(0.0)
100 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
101 : m_aSeriesVector(1)
102 , m_bMaxPointCountDirty(true)
103 , m_nMaxPointCount(0)
104 , m_aListOfCachedYValues()
106 m_aSeriesVector[0] = std::move(pSeries);
109 VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
110 : m_aSeriesVector( std::move(other.m_aSeriesVector) )
111 , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
112 , m_nMaxPointCount( other.m_nMaxPointCount )
113 , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
117 VDataSeriesGroup::~VDataSeriesGroup()
121 void VDataSeriesGroup::deleteSeries()
123 //delete all data series help objects:
124 m_aSeriesVector.clear();
127 void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
129 m_aSeriesVector.push_back(std::move(pSeries));
130 m_bMaxPointCountDirty=true;
133 sal_Int32 VDataSeriesGroup::getSeriesCount() const
135 return m_aSeriesVector.size();
138 VSeriesPlotter::VSeriesPlotter( const uno::Reference<XChartType>& xChartTypeModel
139 , sal_Int32 nDimensionCount, bool bCategoryXAxis )
140 : PlotterBase( nDimensionCount )
141 , m_pMainPosHelper( nullptr )
142 , m_xChartTypeModel(xChartTypeModel)
143 , m_xChartTypeModelProps( uno::Reference< beans::XPropertySet >::query( xChartTypeModel ))
144 , m_aZSlots()
145 , m_bCategoryXAxis(bCategoryXAxis)
146 , m_nTimeResolution(css::chart::TimeUnit::DAY)
147 , m_aNullDate(30,12,1899)
148 , m_xColorScheme()
149 , m_pExplicitCategoriesProvider(nullptr)
150 , m_bPointsWereSkipped(false)
152 SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
155 VSeriesPlotter::~VSeriesPlotter()
157 //delete all data series help objects:
158 for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
160 for (VDataSeriesGroup & rGroup : rGroupVector)
162 rGroup.deleteSeries();
164 rGroupVector.clear();
166 m_aZSlots.clear();
168 m_aSecondaryPosHelperMap.clear();
170 m_aSecondaryValueScales.clear();
173 void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
175 //take ownership of pSeries
177 OSL_PRECOND( pSeries, "series to add is NULL" );
178 if(!pSeries)
179 return;
181 if(m_bCategoryXAxis)
183 if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
184 pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
185 else
186 pSeries->setCategoryXAxis();
188 else
190 if( m_pExplicitCategoriesProvider )
191 pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
194 if(zSlot<0 || zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
196 //new z slot
197 std::vector< VDataSeriesGroup > aZSlot;
198 aZSlot.emplace_back( std::move(pSeries) );
199 m_aZSlots.push_back( std::move(aZSlot) );
201 else
203 //existing zslot
204 std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
206 if(xSlot<0 || xSlot>=static_cast<sal_Int32>(rXSlots.size()))
208 //append the series to already existing x series
209 rXSlots.emplace_back( std::move(pSeries) );
211 else
213 //x slot is already occupied
214 //y slot decides what to do:
216 VDataSeriesGroup& rYSlots = rXSlots[xSlot];
217 sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
219 if( ySlot < -1 )
221 //move all existing series in the xSlot to next slot
222 //@todo
223 OSL_FAIL( "Not implemented yet");
225 else if( ySlot == -1 || ySlot >= nYSlotCount)
227 //append the series to already existing y series
228 rYSlots.addSeries( std::move(pSeries) );
230 else
232 //y slot is already occupied
233 //insert at given y and x position
235 //@todo
236 OSL_FAIL( "Not implemented yet");
242 drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
244 drawing::Direction3D aRet(1.0,1.0,1.0);
245 if (!m_pPosHelper)
246 return aRet;
248 drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
249 aRet.DirectionZ = aScale.DirectionZ*0.2;
250 if(aRet.DirectionZ>1.0)
251 aRet.DirectionZ=1.0;
252 if(aRet.DirectionZ>10)
253 aRet.DirectionZ=10;
254 return aRet;
257 void VSeriesPlotter::releaseShapes()
259 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
261 for (VDataSeriesGroup const & rGroup : rGroupVector)
263 //iterate through all series in this x slot
264 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
266 pSeries->releaseShapes();
272 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
273 , const uno::Reference< drawing::XShapes >& xTarget )
275 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xGroupShape );
276 if( !xShapes.is() )
278 //create a group shape for this series and add to logic target:
279 xShapes = createGroupShape( xTarget,pDataSeries->getCID() );
280 pDataSeries->m_xGroupShape = xShapes;
282 return xShapes;
285 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
286 , const uno::Reference< drawing::XShapes >& xTarget )
288 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xFrontSubGroupShape );
289 if(!xShapes.is())
291 //ensure that the series group shape is already created
292 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
293 //ensure that the back child is created first
294 getSeriesGroupShapeBackChild( pDataSeries, xTarget );
295 //use series group shape as parent for the new created front group shape
296 xShapes = createGroupShape( xSeriesShapes );
297 pDataSeries->m_xFrontSubGroupShape = xShapes;
299 return xShapes;
302 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
303 , const uno::Reference< drawing::XShapes >& xTarget )
305 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xBackSubGroupShape );
306 if(!xShapes.is())
308 //ensure that the series group shape is already created
309 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
310 //use series group shape as parent for the new created back group shape
311 xShapes = createGroupShape( xSeriesShapes );
312 pDataSeries->m_xBackSubGroupShape = xShapes;
314 return xShapes;
317 uno::Reference< drawing::XShapes > VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
318 , const uno::Reference< drawing::XShapes >& xTextTarget )
320 //xTextTarget needs to be a 2D shape container always!
322 uno::Reference< drawing::XShapes > xShapes( rDataSeries.m_xLabelsGroupShape );
323 if(!xShapes.is())
325 //create a 2D group shape for texts of this series and add to text target:
326 xShapes = m_pShapeFactory->createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
327 rDataSeries.m_xLabelsGroupShape = xShapes;
329 return xShapes;
332 uno::Reference< drawing::XShapes > VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
333 , const uno::Reference< drawing::XShapes >& xTarget
334 , bool bYError )
336 uno::Reference< css::drawing::XShapes > &rShapeGroup =
337 bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
339 uno::Reference< drawing::XShapes > xShapes( rShapeGroup );
340 if(!xShapes.is())
342 //create a group shape for this series and add to logic target:
343 xShapes = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
344 rShapeGroup = xShapes;
346 return xShapes;
350 OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
351 , sal_Int32 nPointIndex
352 , double fValue
353 , bool bAsPercentage )
355 OUString aNumber;
357 if (m_apNumberFormatterWrapper)
359 sal_Int32 nNumberFormatKey = 0;
360 if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
361 nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
362 else if( bAsPercentage )
364 sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
365 if( nPercentFormat != -1 )
366 nNumberFormatKey = nPercentFormat;
368 else
370 if( rDataSeries.shouldLabelNumberFormatKeyBeDetectedFromYAxis() && m_aAxesNumberFormats.hasFormat(1,rDataSeries.getAttachedAxisIndex()) ) //y-axis
371 nNumberFormatKey = m_aAxesNumberFormats.getFormat(1,rDataSeries.getAttachedAxisIndex());
372 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 // pt -> 1/100th mm
456 fViewFontSize *= (2540.0f / 72.0f);
459 // the font height is used for computing the size of an optional legend
460 // symbol to be prepended to the text label.
461 Reference< drawing::XShape > xSymbol;
462 if(pLabel->ShowLegendSymbol)
464 sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
465 awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
466 sal_Int32 nSymbolWidth = aCurrentRatio.Width;
467 if( aCurrentRatio.Height > 0 )
469 nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
471 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
473 if( rDataSeries.isVaryColorsByPoint() )
474 xSymbol.set( VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_, m_xShapeFactory ) );
475 else
476 xSymbol.set( VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_, m_xShapeFactory ) );
479 //prepare text
480 bool bTextWrap = false;
481 OUString aSeparator(" ");
482 double fRotationDegrees = 0.0;
485 uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
486 if(xPointProps.is())
488 xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
489 xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
490 // Extract the optional text rotation through the
491 // "TextRotation" property attached to the passed data point.
492 xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
495 catch( const uno::Exception& )
497 TOOLS_WARN_EXCEPTION("chart2", "" );
500 sal_Int32 nLineCountForSymbolsize = 0;
501 sal_uInt32 nTextListLength = 3;
502 sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
503 Sequence< OUString > aTextList( nTextListLength );
505 bool bUseCustomLabel = nCustomLabelsCount > 0;
506 if( bUseCustomLabel )
508 nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
509 aSeparator = "";
510 aTextList = Sequence< OUString >( nTextListLength );
511 for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
513 switch( aCustomLabels[i]->getFieldType() )
515 case DataPointCustomLabelFieldType_VALUE:
517 aTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
518 break;
520 case DataPointCustomLabelFieldType_CATEGORYNAME:
522 aTextList[i] = getCategoryName( nPointIndex );
523 break;
525 case DataPointCustomLabelFieldType_SERIESNAME:
527 OUString aRole;
528 if ( m_xChartTypeModel )
529 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
530 const uno::Reference< XDataSeries >& xSeries( rDataSeries.getModel() );
531 aTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
532 break;
534 case DataPointCustomLabelFieldType_CELLREF:
536 // TODO: for now doesn't show placeholder
537 aTextList[i] = OUString();
538 break;
540 case DataPointCustomLabelFieldType_TEXT:
542 aTextList[i] = aCustomLabels[i]->getString();
543 break;
545 case DataPointCustomLabelFieldType_NEWLINE:
547 aTextList[i] = "\n";
548 break;
550 default:
551 break;
553 aCustomLabels[i]->setString( aTextList[i] );
556 else
558 if( pLabel->ShowCategoryName )
560 aTextList[0] = getCategoryName( nPointIndex );
563 if( pLabel->ShowNumber )
565 aTextList[1] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
568 if( pLabel->ShowNumberInPercent )
570 if(fSumValue==0.0)
571 fSumValue=1.0;
572 fValue /= fSumValue;
573 if( fValue < 0 )
574 fValue*=-1.0;
576 aTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
580 for( sal_Int32 nN = 0; nN < aTextList.getLength(); ++nN )
582 if( !aTextList[nN].isEmpty() )
584 ++nLineCountForSymbolsize;
588 //prepare properties for multipropertyset-interface of shape
589 tNameSequence* pPropNames;
590 tAnySequence* pPropValues;
591 if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
592 return xTextShape;
594 // set text alignment for the text shape to be created.
595 LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
597 // check if data series entry percent value and absolute value have to
598 // be appended to the text label, and what should be the separator
599 // character (comma, space, new line). In case it is a new line we get
600 // a multi-line label.
601 bool bMultiLineLabel = ( aSeparator == "\n" );
603 if( bUseCustomLabel )
605 Sequence< uno::Reference< XFormattedString > > aFormattedLabels( aCustomLabels.getLength() );
606 for( int i = 0; i < aFormattedLabels.getLength(); i++ )
608 uno::Reference< XFormattedString > xString( aCustomLabels[i], uno::UNO_QUERY );
609 aFormattedLabels[i] = xString;
612 // center the text
613 sal_uInt32 nProperties = pPropNames->getLength();
614 pPropNames->realloc( nProperties + 1 );
615 pPropValues->realloc( nProperties + 1 );
616 (*pPropNames)[ nProperties ] = UNO_NAME_EDIT_PARA_ADJUST;
617 (*pPropValues)[ nProperties ] <<= style::ParagraphAdjust_CENTER;
619 // create text shape
620 xTextShape = ShapeFactory::getOrCreateShapeFactory( m_xShapeFactory )->
621 createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
622 ShapeFactory::makeTransformation( aScreenPosition2D ) );
624 else if( bMultiLineLabel )
626 // prepare properties for each paragraph
627 // we want to have the value and percent value centered respect
628 // with the category name
629 Sequence< tNameSequence > aParaPropNames(3);
630 aParaPropNames[1].realloc(1);
631 aParaPropNames[1][0] = "ParaAdjust";
632 aParaPropNames[2].realloc(1);
633 aParaPropNames[2][0] = "ParaAdjust";
635 Sequence< tAnySequence > aParaPropValues(3);
636 aParaPropValues[1].realloc(1);
637 aParaPropValues[1][0] <<= style::ParagraphAdjust_CENTER;
638 aParaPropValues[2].realloc(1);
639 aParaPropValues[2][0] <<= style::ParagraphAdjust_CENTER;
641 //create text shape
642 xTextShape = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory)->
643 createText( xTarget_, aTextList, aParaPropNames, aParaPropValues
644 , *pPropNames, *pPropValues, ShapeFactory::makeTransformation( aScreenPosition2D ) );
646 else
648 // join text list elements
649 OUStringBuffer aText;
650 for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
652 if( !aTextList[nN].isEmpty() )
654 if( !aText.isEmpty() )
656 aText.append(aSeparator);
658 aText.append( aTextList[nN] );
662 //create text shape
663 xTextShape = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory)->
664 createText( xTarget_, aText.makeStringAndClear()
665 , *pPropNames, *pPropValues, ShapeFactory::makeTransformation( aScreenPosition2D ) );
668 if( !xTextShape.is() )
669 return xTextShape;
671 // we need to use a default value for the maximum width property ?
672 if( nTextWidth == 0 && bTextWrap )
674 sal_Int32 nMinSize =
675 (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
676 ? m_aPageReferenceSize.Height
677 : m_aPageReferenceSize.Width;
678 nTextWidth = nMinSize / 3;
681 // in case text must be wrapped set the maximum width property
682 // for the text shape
683 if( nTextWidth != 0 && bTextWrap )
685 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
686 if( xProp.is() )
688 // compute the height of a line of text
689 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
691 nLineCountForSymbolsize = 1;
693 awt::Size aTextSize = xTextShape->getSize();
694 sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
696 // set maximum text width
697 uno::Any aTextMaximumFrameWidth( nTextWidth );
698 xProp->setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
700 // compute the total lines of text
701 aTextSize = xTextShape->getSize();
702 nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
706 // in case text is rotated, the transformation property of the text
707 // shape is modified.
708 const awt::Point aUnrotatedTextPos( xTextShape->getPosition() );
709 if( fRotationDegrees != 0.0 )
711 const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
712 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
713 if( xProp.is() )
714 xProp->setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
715 LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
718 // in case legend symbol has to be displayed, text shape position is
719 // slightly changed.
720 if( xSymbol.is() )
722 const awt::Point aOldTextPos( xTextShape->getPosition() );
723 awt::Point aNewTextPos( aOldTextPos );
725 awt::Point aSymbolPosition( aUnrotatedTextPos );
726 awt::Size aSymbolSize( xSymbol->getSize() );
727 awt::Size aTextSize = xTextShape->getSize();
729 sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
730 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
731 nLineCountForSymbolsize = 1;
732 aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
734 if(eAlignment==LABEL_ALIGN_LEFT
735 || eAlignment==LABEL_ALIGN_LEFT_TOP
736 || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
738 aSymbolPosition.X -= nXDiff;
740 else if(eAlignment==LABEL_ALIGN_RIGHT
741 || eAlignment==LABEL_ALIGN_RIGHT_TOP
742 || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
744 aNewTextPos.X += nXDiff;
746 else if(eAlignment==LABEL_ALIGN_TOP
747 || eAlignment==LABEL_ALIGN_BOTTOM
748 || eAlignment==LABEL_ALIGN_CENTER )
750 aSymbolPosition.X -= nXDiff/2;
751 aNewTextPos.X += nXDiff/2;
754 xSymbol->setPosition( aSymbolPosition );
755 xTextShape->setPosition( aNewTextPos );
758 catch( const uno::Exception& )
760 TOOLS_WARN_EXCEPTION("chart2", "" );
763 return xTextShape;
766 namespace
768 double lcl_getErrorBarLogicLength(
769 const uno::Sequence< double > & rData,
770 const uno::Reference< beans::XPropertySet >& xProp,
771 sal_Int32 nErrorBarStyle,
772 sal_Int32 nIndex,
773 bool bPositive,
774 bool bYError )
776 double fResult;
777 ::rtl::math::setNan( & fResult );
780 switch( nErrorBarStyle )
782 case css::chart::ErrorBarStyle::NONE:
783 break;
784 case css::chart::ErrorBarStyle::VARIANCE:
785 fResult = StatisticsHelper::getVariance( rData );
786 break;
787 case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
788 fResult = StatisticsHelper::getStandardDeviation( rData );
789 break;
790 case css::chart::ErrorBarStyle::RELATIVE:
792 double fPercent = 0;
793 if( xProp->getPropertyValue( bPositive
794 ? OUString("PositiveError")
795 : OUString("NegativeError") ) >>= fPercent )
797 if( nIndex >=0 && nIndex < rData.getLength() &&
798 ! ::rtl::math::isNan( rData[nIndex] ) &&
799 ! ::rtl::math::isNan( fPercent ))
801 fResult = rData[nIndex] * fPercent / 100.0;
805 break;
806 case css::chart::ErrorBarStyle::ABSOLUTE:
807 xProp->getPropertyValue( bPositive
808 ? OUString("PositiveError")
809 : OUString("NegativeError") ) >>= fResult;
810 break;
811 case css::chart::ErrorBarStyle::ERROR_MARGIN:
813 // todo: check if this is really what's called error-margin
814 double fPercent = 0;
815 if( xProp->getPropertyValue( bPositive
816 ? OUString("PositiveError")
817 : OUString("NegativeError") ) >>= fPercent )
819 double fMaxValue;
820 ::rtl::math::setInf(&fMaxValue, true);
821 const double* pValues = rData.getConstArray();
822 for(sal_Int32 i=0; i<rData.getLength(); ++i, ++pValues)
824 if(fMaxValue<*pValues)
825 fMaxValue=*pValues;
827 if( ::rtl::math::isFinite( fMaxValue ) &&
828 ::rtl::math::isFinite( fPercent ))
830 fResult = fMaxValue * fPercent / 100.0;
834 break;
835 case css::chart::ErrorBarStyle::STANDARD_ERROR:
836 fResult = StatisticsHelper::getStandardError( rData );
837 break;
838 case css::chart::ErrorBarStyle::FROM_DATA:
840 uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
841 if( xErrorBarData.is())
842 fResult = StatisticsHelper::getErrorFromDataSource(
843 xErrorBarData, nIndex, bPositive, bYError);
845 break;
848 catch( const uno::Exception & )
850 TOOLS_WARN_EXCEPTION("chart2", "" );
853 return fResult;
856 void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
857 , drawing::PolyPolygonShape3D& rPoly, sal_Int32 nSequenceIndex )
859 double fFixedWidth = 200.0;
861 aMainDirection.normalize();
862 ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
863 aOrthoDirection.normalize();
865 ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
866 ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
867 ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
869 AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
870 AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
873 ::basegfx::B2DVector lcl_getErrorBarMainDirection(
874 const drawing::Position3D& rStart
875 , const drawing::Position3D& rBottomEnd
876 , PlottingPositionHelper const * pPosHelper
877 , const drawing::Position3D& rUnscaledLogicPosition
878 , bool bYError )
880 ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
881 , rStart.PositionY - rBottomEnd.PositionY );
882 if( !aMainDirection.getLength() )
884 //get logic clip values:
885 double MinX = pPosHelper->getLogicMinX();
886 double MinY = pPosHelper->getLogicMinY();
887 double MaxX = pPosHelper->getLogicMaxX();
888 double MaxY = pPosHelper->getLogicMaxY();
889 double fZ = pPosHelper->getLogicMinZ();
891 if( bYError )
893 //main direction has constant x value
894 MinX = rUnscaledLogicPosition.PositionX;
895 MaxX = rUnscaledLogicPosition.PositionX;
897 else
899 //main direction has constant y value
900 MinY = rUnscaledLogicPosition.PositionY;
901 MaxY = rUnscaledLogicPosition.PositionY;
904 drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
905 drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
907 aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
908 , aStart.PositionY - aEnd.PositionY );
910 if( !aMainDirection.getLength() )
912 //@todo
914 return aMainDirection;
917 drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
918 , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
920 if(!pPosHelper)
921 return drawing::Position3D(0,0,0);
922 pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
923 pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
924 return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
927 } // anonymous namespace
929 void VSeriesPlotter::createErrorBar(
930 const uno::Reference< drawing::XShapes >& xTarget
931 , const drawing::Position3D& rUnscaledLogicPosition
932 , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
933 , const VDataSeries& rVDataSeries
934 , sal_Int32 nIndex
935 , bool bYError /* = true */
936 , const double* pfScaledLogicX
939 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
940 return;
942 if( ! xErrorBarProperties.is())
943 return;
947 bool bShowPositive = false;
948 bool bShowNegative = false;
949 sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
951 xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
952 xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
953 xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
955 if(!bShowPositive && !bShowNegative)
956 return;
958 if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
959 return;
961 if (!m_pPosHelper)
962 return;
964 drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
965 if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
967 if (bYError)
968 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
969 else
970 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
973 bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
974 bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
975 drawing::Position3D aMiddle(aUnscaledLogicPosition);
976 const double fX = aUnscaledLogicPosition.PositionX;
977 const double fY = aUnscaledLogicPosition.PositionY;
978 const double fZ = aUnscaledLogicPosition.PositionZ;
979 double fScaledX = fX;
980 if( pfScaledLogicX )
981 fScaledX = *pfScaledLogicX;
982 else
983 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
985 aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
987 drawing::Position3D aNegative(aMiddle);
988 drawing::Position3D aPositive(aMiddle);
990 uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
992 if( bShowPositive )
994 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
995 if( ::rtl::math::isFinite( fLength ) )
997 double fLocalX = fX;
998 double fLocalY = fY;
999 if( bYError )
1001 fLocalY+=fLength;
1002 aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1004 else
1006 fLocalX+=fLength;
1007 aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1009 bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
1011 else
1012 bShowPositive = false;
1015 if( bShowNegative )
1017 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
1018 if( ::rtl::math::isFinite( fLength ) )
1020 double fLocalX = fX;
1021 double fLocalY = fY;
1022 if( bYError )
1024 fLocalY-=fLength;
1025 aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1027 else
1029 fLocalX-=fLength;
1030 aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1032 bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
1034 else
1035 bShowNegative = false;
1038 if(!bShowPositive && !bShowNegative)
1039 return;
1041 drawing::PolyPolygonShape3D aPoly;
1043 sal_Int32 nSequenceIndex=0;
1044 if( bShowNegative )
1045 AddPointToPoly( aPoly, aNegative, nSequenceIndex );
1046 AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
1047 if( bShowPositive )
1048 AddPointToPoly( aPoly, aPositive, nSequenceIndex );
1050 if( bShowNegative && bCreateNegativeBorder )
1052 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
1053 nSequenceIndex++;
1054 lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
1056 if( bShowPositive && bCreatePositiveBorder )
1058 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
1059 nSequenceIndex++;
1060 lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
1063 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D( xTarget, PolyToPointSequence( aPoly) );
1064 setMappedProperties( xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
1066 catch( const uno::Exception & )
1068 TOOLS_WARN_EXCEPTION("chart2", "" );
1073 void VSeriesPlotter::addErrorBorder(
1074 const drawing::Position3D& rPos0
1075 ,const drawing::Position3D& rPos1
1076 ,const uno::Reference< drawing::XShapes >& rTarget
1077 ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
1079 drawing::PolyPolygonShape3D aPoly;
1080 sal_Int32 nSequenceIndex = 0;
1081 AddPointToPoly( aPoly, rPos0, nSequenceIndex );
1082 AddPointToPoly( aPoly, rPos1, nSequenceIndex );
1083 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1084 rTarget, PolyToPointSequence( aPoly) );
1085 setMappedProperties( xShape, rErrorBorderProp,
1086 PropertyMapper::getPropertyNameMapForLineProperties() );
1089 void VSeriesPlotter::createErrorRectangle(
1090 const drawing::Position3D& rUnscaledLogicPosition
1091 ,VDataSeries& rVDataSeries
1092 ,sal_Int32 nIndex
1093 ,const uno::Reference< drawing::XShapes >& rTarget
1094 ,bool bUseXErrorData
1095 ,bool bUseYErrorData )
1097 if ( m_nDimension != 2 )
1098 return;
1100 // error border properties
1101 Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
1102 if ( bUseXErrorData )
1104 xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
1105 if ( !xErrorBorderPropX.is() )
1106 return;
1108 uno::Reference< drawing::XShapes > xErrorBorder_ShapesX(
1109 getErrorBarsGroupShape( rVDataSeries, rTarget, false ) );
1111 if ( bUseYErrorData )
1113 xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
1114 if ( !xErrorBorderPropY.is() )
1115 return;
1117 uno::Reference< drawing::XShapes > xErrorBorder_ShapesY(
1118 getErrorBarsGroupShape( rVDataSeries, rTarget, true ) );
1120 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
1121 return;
1125 bool bShowXPositive = false;
1126 bool bShowXNegative = false;
1127 bool bShowYPositive = false;
1128 bool bShowYNegative = false;
1130 sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
1131 sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
1133 if ( bUseXErrorData )
1135 xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
1136 xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
1137 xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
1139 if ( bUseYErrorData )
1141 xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
1142 xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
1143 xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
1146 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
1147 bUseXErrorData = false;
1148 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
1149 bUseYErrorData = false;
1151 if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
1152 return;
1154 if ( !m_pPosHelper )
1155 return;
1157 drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
1158 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1159 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
1160 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1161 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
1163 const double fX = aUnscaledLogicPosition.PositionX;
1164 const double fY = aUnscaledLogicPosition.PositionY;
1165 const double fZ = aUnscaledLogicPosition.PositionZ;
1166 double fScaledX = fX;
1167 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1169 uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
1170 uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
1172 double fPosX = 0.0;
1173 double fPosY = 0.0;
1174 double fNegX = 0.0;
1175 double fNegY = 0.0;
1176 if ( bUseXErrorData )
1178 if ( bShowXPositive )
1179 fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1180 nErrorBorderStyleX, nIndex, true, false );
1181 if ( bShowXNegative )
1182 fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1183 nErrorBorderStyleX, nIndex, false, false );
1186 if ( bUseYErrorData )
1188 if ( bShowYPositive )
1189 fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1190 nErrorBorderStyleY, nIndex, true, true );
1191 if ( bShowYNegative )
1192 fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1193 nErrorBorderStyleY, nIndex, false, true );
1196 if ( !( ::rtl::math::isFinite( fPosX ) &&
1197 ::rtl::math::isFinite( fPosY ) &&
1198 ::rtl::math::isFinite( fNegX ) &&
1199 ::rtl::math::isFinite( fNegY ) ) )
1200 return;
1202 drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
1203 fX - fNegX, fY - fNegY, fZ ) );
1204 drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
1205 fX - fNegX, fY + fPosY, fZ ) );
1206 drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
1207 fX + fPosX, fY + fPosY, fZ ) );
1208 drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
1209 fX + fPosX, fY - fNegY, fZ ) );
1210 if ( bUseXErrorData )
1212 // top border
1213 addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
1215 // bottom border
1216 addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
1219 if ( bUseYErrorData )
1221 // left border
1222 addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
1224 // right border
1225 addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
1228 catch( const uno::Exception & )
1230 DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
1234 void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
1235 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1236 , const uno::Reference< drawing::XShapes >& xTarget )
1238 if(m_nDimension!=2)
1239 return;
1240 // error bars
1241 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
1242 if( xErrorBarProp.is())
1244 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1245 getErrorBarsGroupShape(rVDataSeries, xTarget, false) );
1247 createErrorBar( xErrorBarsGroup_Shapes
1248 , rUnscaledLogicPosition, xErrorBarProp
1249 , rVDataSeries, nPointIndex
1250 , false /* bYError */
1251 , nullptr );
1255 void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
1256 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1257 , const uno::Reference< drawing::XShapes >& xTarget
1258 , double const * pfScaledLogicX )
1260 if(m_nDimension!=2)
1261 return;
1262 // error bars
1263 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
1264 if( xErrorBarProp.is())
1266 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1267 getErrorBarsGroupShape(rVDataSeries, xTarget, true) );
1269 createErrorBar( xErrorBarsGroup_Shapes
1270 , rUnscaledLogicPosition, xErrorBarProp
1271 , rVDataSeries, nPointIndex
1272 , true /* bYError */
1273 , pfScaledLogicX );
1277 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
1278 const uno::Reference< drawing::XShapes >& xTarget,
1279 const uno::Reference< drawing::XShapes >& xEquationTarget,
1280 bool bMaySkipPoints )
1282 if(m_nDimension!=2)
1283 return;
1284 uno::Reference< XRegressionCurveContainer > xContainer( rVDataSeries.getModel(), uno::UNO_QUERY );
1285 if(!xContainer.is())
1286 return;
1288 if (!m_pPosHelper)
1289 return;
1291 uno::Sequence< uno::Reference< XRegressionCurve > > aCurveList = xContainer->getRegressionCurves();
1293 for(sal_Int32 nN=0; nN<aCurveList.getLength(); nN++)
1295 uno::Reference< XRegressionCurveCalculator > xCalculator( aCurveList[nN]->getCalculator() );
1296 if( !xCalculator.is())
1297 continue;
1299 uno::Reference< beans::XPropertySet > xProperties( aCurveList[nN], uno::UNO_QUERY );
1301 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurveList[nN] );
1303 sal_Int32 aDegree = 2;
1304 sal_Int32 aPeriod = 2;
1305 double aExtrapolateForward = 0.0;
1306 double aExtrapolateBackward = 0.0;
1307 bool bForceIntercept = false;
1308 double aInterceptValue = 0.0;
1310 if ( xProperties.is() && !bAverageLine )
1312 xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
1313 xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
1314 xProperties->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
1315 xProperties->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
1316 xProperties->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
1317 if (bForceIntercept)
1318 xProperties->getPropertyValue( "InterceptValue") >>= aInterceptValue;
1321 double fChartMinX = m_pPosHelper->getLogicMinX();
1322 double fChartMaxX = m_pPosHelper->getLogicMaxX();
1324 double fMinX = fChartMinX;
1325 double fMaxX = fChartMaxX;
1327 double fPointScale = 1.0;
1329 if( !bAverageLine )
1331 rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
1332 fMaxX += aExtrapolateForward;
1333 fMinX -= aExtrapolateBackward;
1335 fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
1336 // sanitize the value, tdf#119922
1337 fPointScale = std::min(fPointScale, 1000.0);
1340 xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod);
1341 xCalculator->recalculateRegression( rVDataSeries.getAllX(), rVDataSeries.getAllY() );
1342 sal_Int32 nPointCount = 100 * fPointScale;
1344 if ( nPointCount < 2 )
1345 nPointCount = 2;
1347 std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
1348 uno::Reference< chart2::XScaling > xScalingX;
1349 uno::Reference< chart2::XScaling > xScalingY;
1350 if( aScales.size() >= 2 )
1352 xScalingX.set( aScales[0].Scaling );
1353 xScalingY.set( aScales[1].Scaling );
1356 uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
1357 xCalculator->getCurveValues(
1358 fMinX, fMaxX, nPointCount,
1359 xScalingX, xScalingY, bMaySkipPoints ));
1361 nPointCount = aCalculatedPoints.getLength();
1363 drawing::PolyPolygonShape3D aRegressionPoly;
1364 aRegressionPoly.SequenceX.realloc(1);
1365 aRegressionPoly.SequenceY.realloc(1);
1366 aRegressionPoly.SequenceZ.realloc(1);
1367 aRegressionPoly.SequenceX[0].realloc(nPointCount);
1368 aRegressionPoly.SequenceY[0].realloc(nPointCount);
1369 aRegressionPoly.SequenceZ[0].realloc(nPointCount);
1371 sal_Int32 nRealPointCount = 0;
1373 for(sal_Int32 nP = 0; nP < aCalculatedPoints.getLength(); ++nP)
1375 double fLogicX = aCalculatedPoints[nP].X;
1376 double fLogicY = aCalculatedPoints[nP].Y;
1377 double fLogicZ = 0.0; //dummy
1379 // fdo#51656: don't scale mean value lines
1380 if(!bAverageLine)
1381 m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
1383 if(!rtl::math::isNan(fLogicX) && !rtl::math::isInf(fLogicX) &&
1384 !rtl::math::isNan(fLogicY) && !rtl::math::isInf(fLogicY) &&
1385 !rtl::math::isNan(fLogicZ) && !rtl::math::isInf(fLogicZ) )
1387 aRegressionPoly.SequenceX[0][nRealPointCount] = fLogicX;
1388 aRegressionPoly.SequenceY[0][nRealPointCount] = fLogicY;
1389 nRealPointCount++;
1392 aRegressionPoly.SequenceX[0].realloc(nRealPointCount);
1393 aRegressionPoly.SequenceY[0].realloc(nRealPointCount);
1394 aRegressionPoly.SequenceZ[0].realloc(nRealPointCount);
1396 drawing::PolyPolygonShape3D aClippedPoly;
1397 Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
1398 aRegressionPoly = aClippedPoly;
1399 m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
1401 awt::Point aDefaultPos;
1402 if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
1404 VLineProperties aVLineProperties;
1405 aVLineProperties.initFromPropertySet( xProperties );
1407 //create an extra group shape for each curve for selection handling
1408 uno::Reference< drawing::XShapes > xRegressionGroupShapes =
1409 createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
1410 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1411 xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
1412 ShapeFactory::setShapeName( xShape, "MarkHandles" );
1413 aDefaultPos = xShape->getPosition();
1416 // curve equation and correlation coefficient
1417 uno::Reference< beans::XPropertySet > xEquationProperties( aCurveList[nN]->getEquationProperties());
1418 if( xEquationProperties.is())
1420 createRegressionCurveEquationShapes(
1421 rVDataSeries.getDataCurveEquationCID( nN ),
1422 xEquationProperties, xEquationTarget, xCalculator,
1423 aDefaultPos );
1428 static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
1430 const sal_Int32 nStringLength = aString.getLength();
1431 sal_Int32 nMaxLineLength = 0;
1433 for ( sal_Int32 i=0; i<nStringLength; i++ )
1435 sal_Int32 indexSep = aString.indexOf( "\n", i );
1436 if ( indexSep < 0 )
1437 indexSep = nStringLength;
1438 sal_Int32 nLineLength = indexSep - i;
1439 if ( nLineLength > nMaxLineLength )
1440 nMaxLineLength = nLineLength;
1441 i = indexSep;
1444 return nMaxLineLength;
1447 void VSeriesPlotter::createRegressionCurveEquationShapes(
1448 const OUString & rEquationCID,
1449 const uno::Reference< beans::XPropertySet > & xEquationProperties,
1450 const uno::Reference< drawing::XShapes >& xEquationTarget,
1451 const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
1452 awt::Point aDefaultPos )
1454 OSL_ASSERT( xEquationProperties.is());
1455 if( !xEquationProperties.is())
1456 return;
1458 bool bShowEquation = false;
1459 bool bShowCorrCoeff = false;
1460 if(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
1461 ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff ))
1463 if( ! (bShowEquation || bShowCorrCoeff))
1464 return;
1466 OUStringBuffer aFormula;
1467 sal_Int32 nNumberFormatKey = 0;
1468 sal_Int32 nFormulaWidth = 0;
1469 xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
1470 bool bResizeEquation = true;
1471 sal_Int32 nMaxIteration = 2;
1472 if ( bShowEquation )
1474 OUString aXName, aYName;
1475 if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
1476 aXName = OUString( "x" );
1477 if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
1478 aYName = OUString( "f(x)" );
1479 xRegressionCurveCalculator->setXYNames( aXName, aYName );
1482 for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
1484 bResizeEquation = false;
1485 if( bShowEquation )
1487 if (m_apNumberFormatterWrapper)
1488 { // iteration 0: default representation (no wrap)
1489 // iteration 1: expected width (nFormulaWidth) is calculated
1490 aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
1491 m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
1492 nNumberFormatKey, nFormulaWidth );
1493 nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
1495 else
1497 aFormula = xRegressionCurveCalculator->getRepresentation();
1500 if( bShowCorrCoeff )
1502 aFormula.append( "\n" );
1505 if( bShowCorrCoeff )
1507 aFormula.append( "R" ).append( OUStringChar( aSuperscriptFigures[2] ) ).append( " = " );
1508 double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
1509 if (m_apNumberFormatterWrapper)
1511 Color nLabelCol;
1512 bool bColChanged;
1513 aFormula.append(
1514 m_apNumberFormatterWrapper->getFormattedString(
1515 nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
1516 //@todo: change color of label if bColChanged is true
1518 else
1520 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
1521 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
1522 sal_Unicode aDecimalSep = aNumDecimalSep[0];
1523 aFormula.append( ::rtl::math::doubleToUString(
1524 fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
1528 awt::Point aScreenPosition2D;
1529 chart2::RelativePosition aRelativePosition;
1530 if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
1532 //@todo decide whether x is primary or secondary
1533 double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
1534 double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
1535 aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
1536 aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
1538 else
1539 aScreenPosition2D = aDefaultPos;
1541 if( !aFormula.isEmpty())
1543 // set fill and line properties on creation
1544 tNameSequence aNames;
1545 tAnySequence aValues;
1546 PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
1548 uno::Reference< drawing::XShape > xTextShape = m_pShapeFactory->createText(
1549 xEquationTarget, aFormula.makeStringAndClear(),
1550 aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
1552 OSL_ASSERT( xTextShape.is());
1553 if( xTextShape.is())
1555 ShapeFactory::setShapeName( xTextShape, rEquationCID );
1556 awt::Size aSize( xTextShape->getSize() );
1557 awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
1558 aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
1559 //ensure that the equation is fully placed within the page (if possible)
1560 if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
1561 aPos.X = m_aPageReferenceSize.Width - aSize.Width;
1562 if( aPos.X < 0 )
1564 aPos.X = 0;
1565 if ( nFormulaWidth > 0 )
1567 bResizeEquation = true;
1568 if ( nCountIteration < nMaxIteration-1 )
1569 xEquationTarget->remove( xTextShape ); // remove equation
1570 nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
1571 nFormulaWidth -= nCountIteration;
1572 if ( nFormulaWidth < 0 )
1573 nFormulaWidth = 0;
1576 if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
1577 aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
1578 if( aPos.Y < 0 )
1579 aPos.Y = 0;
1580 if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
1581 xTextShape->setPosition(aPos); // if equation was not removed
1588 void VSeriesPlotter::setMappedProperties(
1589 const uno::Reference< drawing::XShape >& xTargetShape
1590 , const uno::Reference< beans::XPropertySet >& xSource
1591 , const tPropertyNameMap& rMap
1592 , tPropertyNameValueMap const * pOverwriteMap )
1594 uno::Reference< beans::XPropertySet > xTargetProp( xTargetShape, uno::UNO_QUERY );
1595 PropertyMapper::setMappedProperties(xTargetProp,xSource,rMap,pOverwriteMap);
1598 void VSeriesPlotter::setTimeResolutionOnXAxis( long TimeResolution, const Date& rNullDate )
1600 m_nTimeResolution = TimeResolution;
1601 m_aNullDate = rNullDate;
1604 // MinimumAndMaximumSupplier
1605 long VSeriesPlotter::calculateTimeResolutionOnXAxis()
1607 long nRet = css::chart::TimeUnit::YEAR;
1608 if (!m_pExplicitCategoriesProvider)
1609 return nRet;
1611 const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
1612 if (rDateCategories.empty())
1613 return nRet;
1615 std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
1617 aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !rtl::math::isNan(rDateCategory); });
1618 if (aIt == aEnd)
1619 return nRet;
1621 Date aNullDate(30,12,1899);
1622 if (m_apNumberFormatterWrapper)
1623 aNullDate = m_apNumberFormatterWrapper->getNullDate();
1625 Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
1626 ++aIt;
1627 for(;aIt!=aEnd;++aIt)
1629 if (rtl::math::isNan(*aIt))
1630 continue;
1632 Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
1633 if( nRet == css::chart::TimeUnit::YEAR )
1635 if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
1636 nRet = css::chart::TimeUnit::MONTH;
1638 if( nRet == css::chart::TimeUnit::MONTH )
1640 if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
1641 nRet = css::chart::TimeUnit::DAY;
1643 if( nRet == css::chart::TimeUnit::DAY )
1644 break;
1645 aPrevious=aCurrent;
1648 return nRet;
1650 double VSeriesPlotter::getMinimumX()
1652 double fMinimum, fMaximum;
1653 getMinimumAndMaximiumX( fMinimum, fMaximum );
1654 return fMinimum;
1656 double VSeriesPlotter::getMaximumX()
1658 double fMinimum, fMaximum;
1659 getMinimumAndMaximiumX( fMinimum, fMaximum );
1660 return fMaximum;
1663 double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1665 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1667 double fMinY, fMaxY;
1668 getMinimumAndMaximiumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1669 return fMinY;
1672 double fMinimum, fMaximum;
1673 ::rtl::math::setInf(&fMinimum, false);
1674 ::rtl::math::setInf(&fMaximum, true);
1675 for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
1677 for(VDataSeriesGroup & rXSlot : rXSlots)
1679 double fLocalMinimum, fLocalMaximum;
1680 rXSlot.calculateYMinAndMaxForCategoryRange(
1681 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1682 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1683 , isSeparateStackingForDifferentSigns( 1 )
1684 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1685 if(fMaximum<fLocalMaximum)
1686 fMaximum=fLocalMaximum;
1687 if(fMinimum>fLocalMinimum)
1688 fMinimum=fLocalMinimum;
1691 if(::rtl::math::isInf(fMinimum))
1692 ::rtl::math::setNan(&fMinimum);
1693 return fMinimum;
1696 double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1698 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1700 double fMinY, fMaxY;
1701 getMinimumAndMaximiumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1702 return fMaxY;
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(::rtl::math::isInf(fMaximum))
1725 ::rtl::math::setNan(&fMaximum);
1726 return fMaximum;
1729 double VSeriesPlotter::getMinimumZ()
1731 //this is the default for all charts without a meaningful z axis
1732 return 1.0;
1734 double VSeriesPlotter::getMaximumZ()
1736 if( m_nDimension!=3 || m_aZSlots.empty() )
1737 return getMinimumZ()+1;
1738 return m_aZSlots.size();
1741 namespace
1743 bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
1745 // default implementation: true for Y axes, and for value X axis
1746 if( nDimensionIndex == 0 )
1747 return !bCategoryXAxis;
1748 return nDimensionIndex == 1;
1752 bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
1754 return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1757 bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
1759 // do not expand axes in 3D charts
1760 return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1763 bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
1765 // default implementation: only for Y axis
1766 return nDimensionIndex == 1;
1769 bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
1771 // default implementation: only for Y axis
1772 return nDimensionIndex == 1;
1775 bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
1777 // default implementation: only for Y axis
1778 return nDimensionIndex == 1;
1781 void VSeriesPlotter::getMinimumAndMaximiumX( double& rfMinimum, double& rfMaximum ) const
1783 ::rtl::math::setInf(&rfMinimum, false);
1784 ::rtl::math::setInf(&rfMaximum, true);
1786 for (auto const& ZSlot : m_aZSlots)
1788 for (auto const& XSlot : ZSlot)
1790 double fLocalMinimum, fLocalMaximum;
1791 XSlot.getMinimumAndMaximiumX( fLocalMinimum, fLocalMaximum );
1792 if( !::rtl::math::isNan(fLocalMinimum) && fLocalMinimum< rfMinimum )
1793 rfMinimum = fLocalMinimum;
1794 if( !::rtl::math::isNan(fLocalMaximum) && fLocalMaximum> rfMaximum )
1795 rfMaximum = fLocalMaximum;
1798 if(::rtl::math::isInf(rfMinimum))
1799 ::rtl::math::setNan(&rfMinimum);
1800 if(::rtl::math::isInf(rfMaximum))
1801 ::rtl::math::setNan(&rfMaximum);
1804 void VSeriesPlotter::getMinimumAndMaximiumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
1806 ::rtl::math::setInf(&rfMinY, false);
1807 ::rtl::math::setInf(&rfMaxY, true);
1809 for (auto const& ZSlot : m_aZSlots)
1811 for (auto const& XSlot : ZSlot)
1813 double fLocalMinimum, fLocalMaximum;
1814 XSlot.getMinimumAndMaximiumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
1815 if( !::rtl::math::isNan(fLocalMinimum) && fLocalMinimum< rfMinY )
1816 rfMinY = fLocalMinimum;
1817 if( !::rtl::math::isNan(fLocalMaximum) && fLocalMaximum> rfMaxY )
1818 rfMaxY = fLocalMaximum;
1821 if(::rtl::math::isInf(rfMinY))
1822 ::rtl::math::setNan(&rfMinY);
1823 if(::rtl::math::isInf(rfMaxY))
1824 ::rtl::math::setNan(&rfMaxY);
1827 sal_Int32 VSeriesPlotter::getPointCount() const
1829 sal_Int32 nRet = 0;
1831 for (auto const& ZSlot : m_aZSlots)
1833 for (auto const& XSlot : ZSlot)
1835 sal_Int32 nPointCount = XSlot.getPointCount();
1836 if( nPointCount>nRet )
1837 nRet = nPointCount;
1840 return nRet;
1843 void VSeriesPlotter::setNumberFormatsSupplier(
1844 const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
1846 m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
1849 void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
1851 m_xColorScheme = xColorScheme;
1854 void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
1856 m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
1859 sal_Int32 VDataSeriesGroup::getPointCount() const
1861 if(!m_bMaxPointCountDirty)
1862 return m_nMaxPointCount;
1864 sal_Int32 nRet = 0;
1866 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1868 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1869 if( nPointCount>nRet )
1870 nRet = nPointCount;
1872 m_nMaxPointCount=nRet;
1873 m_aListOfCachedYValues.clear();
1874 m_aListOfCachedYValues.resize(m_nMaxPointCount);
1875 m_bMaxPointCountDirty=false;
1876 return nRet;
1879 sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1881 sal_Int32 nRet = 0;
1883 if (!m_aSeriesVector.empty())
1884 nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
1886 return nRet;
1889 void VDataSeriesGroup::getMinimumAndMaximiumX( double& rfMinimum, double& rfMaximum ) const
1892 ::rtl::math::setInf(&rfMinimum, false);
1893 ::rtl::math::setInf(&rfMaximum, true);
1895 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1897 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1898 for(sal_Int32 nN=0;nN<nPointCount;nN++)
1900 double fX = pSeries->getXValue( nN );
1901 if( ::rtl::math::isNan(fX) )
1902 continue;
1903 if(rfMaximum<fX)
1904 rfMaximum=fX;
1905 if(rfMinimum>fX)
1906 rfMinimum=fX;
1909 if(::rtl::math::isInf(rfMinimum))
1910 ::rtl::math::setNan(&rfMinimum);
1911 if(::rtl::math::isInf(rfMaximum))
1912 ::rtl::math::setNan(&rfMaximum);
1915 namespace {
1918 * Keep track of minimum and maximum Y values for one or more data series.
1919 * When multiple data series exist, that indicates that the data series are
1920 * stacked.
1922 * <p>For each X value, we calculate separate Y value ranges for each data
1923 * series in the first pass. In the second pass, we calculate the minimum Y
1924 * value by taking the absolute minimum value of all data series, whereas
1925 * the maximum Y value is the sum of all the series maximum Y values.</p>
1927 * <p>Once that's done for all X values, the final min / max Y values get
1928 * calculated by taking the absolute min / max Y values across all the X
1929 * values.</p>
1931 class PerXMinMaxCalculator
1933 typedef std::pair<double, double> MinMaxType;
1934 typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
1935 typedef std::map<double, std::unique_ptr<SeriesMinMaxType>> GroupMinMaxType;
1936 typedef std::unordered_map<double, MinMaxType> TotalStoreType;
1937 GroupMinMaxType m_SeriesGroup;
1938 size_t mnCurSeries;
1940 public:
1941 PerXMinMaxCalculator() : mnCurSeries(0) {}
1943 void nextSeries() { ++mnCurSeries; }
1945 void setValue(double fX, double fY)
1947 SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
1948 if (!pStore)
1949 // This shouldn't happen!
1950 return;
1952 SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
1953 if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
1955 MinMaxType& r = it->second;
1956 // A min-max pair already exists for this series. Update it.
1957 if (fY < r.first)
1958 r.first = fY;
1959 if (r.second < fY)
1960 r.second = fY;
1962 else
1964 // No existing pair. Insert a new one.
1965 pStore->insert(
1966 it, SeriesMinMaxType::value_type(
1967 mnCurSeries, MinMaxType(fY,fY)));
1971 void getTotalRange(double& rfMin, double& rfMax) const
1973 rtl::math::setNan(&rfMin);
1974 rtl::math::setNan(&rfMax);
1976 TotalStoreType aStore;
1977 getTotalStore(aStore);
1979 if (aStore.empty())
1980 return;
1982 TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
1983 rfMin = it->second.first;
1984 rfMax = it->second.second;
1985 for (++it; it != itEnd; ++it)
1987 if (rfMin > it->second.first)
1988 rfMin = it->second.first;
1989 if (rfMax < it->second.second)
1990 rfMax = it->second.second;
1994 private:
1996 * Parse all data and reduce them into a set of global Y value ranges per
1997 * X value.
1999 void getTotalStore(TotalStoreType& rStore) const
2001 TotalStoreType aStore;
2002 for (auto const& it : m_SeriesGroup)
2004 double fX = it.first;
2006 const SeriesMinMaxType& rSeries = *it.second;
2007 for (auto const& series : rSeries)
2009 double fYMin = series.second.first, fYMax = series.second.second;
2010 TotalStoreType::iterator itr = aStore.find(fX);
2011 if (itr == aStore.end())
2012 // New min-max pair for give X value.
2013 aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
2014 else
2016 MinMaxType& r = itr->second;
2017 if (fYMin < r.first)
2018 r.first = fYMin; // min y-value
2020 r.second += fYMax; // accumulative max y-value.
2024 rStore.swap(aStore);
2027 SeriesMinMaxType* getByXValue(double fX)
2029 GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
2030 if (it == m_SeriesGroup.end())
2032 std::pair<GroupMinMaxType::iterator,bool> r =
2033 m_SeriesGroup.insert(std::make_pair(fX, std::make_unique<SeriesMinMaxType>()));
2035 if (!r.second)
2036 // insertion failed.
2037 return nullptr;
2039 it = r.first;
2042 return it->second.get();
2048 void VDataSeriesGroup::getMinimumAndMaximiumYInContinuousXRange(
2049 double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
2051 ::rtl::math::setNan(&rfMinY);
2052 ::rtl::math::setNan(&rfMaxY);
2054 if (m_aSeriesVector.empty())
2055 // No data series. Bail out.
2056 return;
2058 PerXMinMaxCalculator aRangeCalc;
2059 for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
2061 if (!pSeries)
2062 continue;
2064 for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
2066 if (nAxisIndex != pSeries->getAttachedAxisIndex())
2067 continue;
2069 double fX = pSeries->getXValue(i);
2070 if (rtl::math::isNan(fX))
2071 continue;
2073 if (fX < fMinX || fX > fMaxX)
2074 // Outside specified X range. Skip it.
2075 continue;
2077 double fY = pSeries->getYValue(i);
2078 if (::rtl::math::isNan(fY))
2079 continue;
2081 aRangeCalc.setValue(fX, fY);
2083 aRangeCalc.nextSeries();
2086 aRangeCalc.getTotalRange(rfMinY, rfMaxY);
2089 void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
2090 , bool bSeparateStackingForDifferentSigns
2091 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2093 assert(nCategoryIndex >= 0);
2094 assert(nCategoryIndex < getPointCount());
2096 ::rtl::math::setInf(&rfMinimumY, false);
2097 ::rtl::math::setInf(&rfMaximumY, true);
2099 if(m_aSeriesVector.empty())
2100 return;
2102 CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
2103 if( !aCachedYValues.m_bValuesDirty )
2105 //return cached values
2106 rfMinimumY = aCachedYValues.m_fMinimumY;
2107 rfMaximumY = aCachedYValues.m_fMaximumY;
2108 return;
2111 double fTotalSum, fPositiveSum, fNegativeSum, fFirstPositiveY, fFirstNegativeY;
2112 ::rtl::math::setNan( &fTotalSum );
2113 ::rtl::math::setNan( &fPositiveSum );
2114 ::rtl::math::setNan( &fNegativeSum );
2115 ::rtl::math::setNan( &fFirstPositiveY );
2116 ::rtl::math::setNan( &fFirstNegativeY );
2118 if( bSeparateStackingForDifferentSigns )
2120 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2122 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2123 continue;
2125 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2126 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2128 if( fValueMaxY >= 0 )
2130 if( ::rtl::math::isNan( fPositiveSum ) )
2131 fPositiveSum = fFirstPositiveY = fValueMaxY;
2132 else
2133 fPositiveSum += fValueMaxY;
2135 if( fValueMinY < 0 )
2137 if(::rtl::math::isNan( fNegativeSum ))
2138 fNegativeSum = fFirstNegativeY = fValueMinY;
2139 else
2140 fNegativeSum += fValueMinY;
2143 rfMinimumY = ::rtl::math::isNan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
2144 rfMaximumY = ::rtl::math::isNan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
2146 else
2148 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2150 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2151 continue;
2153 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2154 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2156 if( ::rtl::math::isNan( fTotalSum ) )
2158 rfMinimumY = fValueMinY;
2159 rfMaximumY = fTotalSum = fValueMaxY;
2161 else
2163 fTotalSum += fValueMaxY;
2164 if( rfMinimumY > fTotalSum )
2165 rfMinimumY = fTotalSum;
2166 if( rfMaximumY < fTotalSum )
2167 rfMaximumY = fTotalSum;
2172 aCachedYValues.m_fMinimumY = rfMinimumY;
2173 aCachedYValues.m_fMaximumY = rfMaximumY;
2174 aCachedYValues.m_bValuesDirty = false;
2175 m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
2178 void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
2179 sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
2180 , bool bSeparateStackingForDifferentSigns
2181 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2183 //@todo maybe cache these values
2184 ::rtl::math::setInf(&rfMinimumY, false);
2185 ::rtl::math::setInf(&rfMaximumY, true);
2187 //iterate through the given categories
2188 if(nStartCategoryIndex<0)
2189 nStartCategoryIndex=0;
2190 const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
2191 if(nPointCount <= 0)
2192 return;
2193 if (nEndCategoryIndex >= nPointCount)
2194 nEndCategoryIndex = nPointCount - 1;
2195 if(nEndCategoryIndex<0)
2196 nEndCategoryIndex=0;
2197 for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
2199 double fMinimumY; ::rtl::math::setNan(&fMinimumY);
2200 double fMaximumY; ::rtl::math::setNan(&fMaximumY);
2202 calculateYMinAndMaxForCategory( nCatIndex
2203 , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
2205 if(rfMinimumY > fMinimumY)
2206 rfMinimumY = fMinimumY;
2207 if(rfMaximumY < fMaximumY)
2208 rfMaximumY = fMaximumY;
2212 double VSeriesPlotter::getTransformedDepth() const
2214 double MinZ = m_pMainPosHelper->getLogicMinZ();
2215 double MaxZ = m_pMainPosHelper->getLogicMaxZ();
2216 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
2217 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
2218 return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
2221 void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
2223 if( nAxisIndex<1 )
2224 return;
2226 m_aSecondaryValueScales[nAxisIndex]=rScale;
2229 PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
2231 PlottingPositionHelper* pRet = nullptr;
2232 if(nAxisIndex>0)
2234 tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
2235 if( aPosIt != m_aSecondaryPosHelperMap.end() )
2237 pRet = aPosIt->second.get();
2239 else if (m_pPosHelper)
2241 tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
2242 if( aScaleIt != m_aSecondaryValueScales.end() )
2244 m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
2245 pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
2249 if( !pRet )
2250 pRet = m_pMainPosHelper;
2251 if(pRet)
2252 pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
2253 return *pRet;
2256 void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
2260 VDataSeries* VSeriesPlotter::getFirstSeries() const
2262 for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
2264 if (!rGroup.empty())
2266 if (!rGroup[0].m_aSeriesVector.empty())
2268 VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
2269 if (pSeries)
2270 return pSeries;
2274 return nullptr;
2277 OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
2279 if (m_pExplicitCategoriesProvider)
2281 Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
2282 if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
2284 return aCategories[nPointIndex];
2287 return OUString();
2290 uno::Sequence< OUString > VSeriesPlotter::getSeriesNames() const
2292 std::vector<OUString> aRetVector;
2294 OUString aRole;
2295 if( m_xChartTypeModel.is() )
2296 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2298 for (auto const& rGroup : m_aZSlots)
2300 if (!rGroup.empty())
2302 VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
2303 if (!rSeriesGroup.m_aSeriesVector.empty())
2305 VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
2306 uno::Reference< XDataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
2307 if( xSeries.is() )
2309 OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
2310 aRetVector.push_back( aSeriesName );
2315 return comphelper::containerToSequence( aRetVector );
2318 void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
2320 m_aPageReferenceSize = rPageRefSize;
2322 // set reference size also at all data series
2324 for (auto const & outer : m_aZSlots)
2325 for (VDataSeriesGroup const & rGroup : outer)
2327 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2329 pSeries->setPageReferenceSize(m_aPageReferenceSize);
2334 //better performance for big data
2335 void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
2337 m_aCoordinateSystemResolution = rCoordinateSystemResolution;
2340 bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
2342 return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
2345 bool VSeriesPlotter::shouldSnapRectToUsedArea()
2347 return m_nDimension != 3;
2350 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
2351 const awt::Size& rEntryKeyAspectRatio
2352 , css::chart::ChartLegendExpansion eLegendExpansion
2353 , const Reference< beans::XPropertySet >& xTextProperties
2354 , const Reference< drawing::XShapes >& xTarget
2355 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2356 , const Reference< uno::XComponentContext >& xContext
2357 , ChartModel& rModel
2360 std::vector< ViewLegendEntry > aResult;
2362 if( xTarget.is() )
2364 uno::Reference< XCoordinateSystemContainer > xCooSysCnt( rModel.getFirstDiagram(), uno::UNO_QUERY );
2365 Reference< chart2::XCoordinateSystem > xCooSys(xCooSysCnt->getCoordinateSystems()[0]);
2366 Reference< beans::XPropertySet > xProp( xCooSys, uno::UNO_QUERY );
2367 bool bSwapXAndY = false;
2369 if( xProp.is()) try
2371 xProp->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
2373 catch( const uno::Exception& )
2377 //iterate through all series
2378 bool bBreak = false;
2379 bool bFirstSeries = true;
2382 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
2384 for (VDataSeriesGroup const & rGroup : rGroupVector)
2386 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2388 if (!pSeries)
2389 continue;
2391 if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
2393 continue;
2396 std::vector<ViewLegendEntry> aSeriesEntries(
2397 createLegendEntriesForSeries(
2398 rEntryKeyAspectRatio, *pSeries, xTextProperties,
2399 xTarget, xShapeFactory, xContext));
2401 //add series entries to the result now
2403 // use only the first series if VaryColorsByPoint is set for the first series
2404 if (bFirstSeries && pSeries->isVaryColorsByPoint())
2405 bBreak = true;
2406 bFirstSeries = false;
2408 // add entries reverse if chart is stacked in y-direction and the legend is not wide.
2409 // If the legend is wide and we have a stacked bar-chart the normal order
2410 // is the correct one, unless the chart type is horizontal bar-chart.
2411 bool bReverse = false;
2412 if( eLegendExpansion != css::chart::ChartLegendExpansion_WIDE )
2414 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2415 bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
2417 if( bSwapXAndY )
2419 bReverse = !bReverse;
2422 else if( bSwapXAndY )
2424 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2425 bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
2428 if (bReverse)
2429 aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
2430 else
2431 aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
2433 if (bBreak)
2434 return aResult;
2439 return aResult;
2442 std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
2444 std::vector<VDataSeries*> aAllSeries;
2445 for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
2447 for(VDataSeriesGroup const & rGroup : rXSlot)
2449 for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
2450 aAllSeries.push_back(p.get());
2453 return aAllSeries;
2456 namespace
2458 bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
2460 bool bHasVisibleLine = false;
2461 rbHasDashedLine = false;
2462 drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
2463 if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
2465 if( aLineStyle != drawing::LineStyle_NONE )
2466 bHasVisibleLine = true;
2467 if( aLineStyle == drawing::LineStyle_DASH )
2468 rbHasDashedLine = true;
2470 return bHasVisibleLine;
2473 bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
2475 bool bHasRegressionCurves = false;
2476 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2477 if( xRegrCont.is())
2479 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves() );
2480 sal_Int32 i = 0, nCount = aCurves.getLength();
2481 for( i=0; i<nCount; ++i )
2483 if( aCurves[i].is() )
2485 bHasRegressionCurves = true;
2486 lcl_HasVisibleLine( uno::Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ), rbHasDashedLine );
2490 return bHasRegressionCurves;
2493 LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
2495 return LegendSymbolStyle::Box;
2498 awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
2500 awt::Size aRet(1000,1000);
2501 if( m_nDimension==3 )
2502 return aRet;
2504 bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
2505 bool bHasLines = false;
2506 bool bHasDashedLines = false;
2507 //iterate through all series
2508 for (VDataSeries* pSeries : getAllSeries())
2510 if( bSeriesAllowsLines )
2512 bool bCurrentDashed = false;
2513 if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
2515 bHasLines = true;
2516 if( bCurrentDashed )
2518 bHasDashedLines = true;
2519 break;
2523 bool bRegressionHasDashedLines=false;
2524 if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
2526 bHasLines = true;
2527 if( bRegressionHasDashedLines )
2529 bHasDashedLines = true;
2530 break;
2534 if( bHasLines )
2536 if( bHasDashedLines )
2537 aRet = awt::Size(1600,-1);
2538 else
2539 aRet = awt::Size(800,-1);
2541 return aRet;
2544 uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
2546 return uno::Any();
2549 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForSeries(
2550 const awt::Size& rEntryKeyAspectRatio
2551 , const VDataSeries& rSeries
2552 , const Reference< drawing::XShapes >& xTarget
2553 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2556 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2557 uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
2559 VLegendSymbolFactory::PropertyType ePropType =
2560 VLegendSymbolFactory::PropertyType::FilledSeries;
2562 // todo: maybe the property-style does not solely depend on the
2563 // legend-symbol type
2564 switch( eLegendSymbolStyle )
2566 case LegendSymbolStyle::Line:
2567 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2568 break;
2569 default:
2570 break;
2572 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2573 xTarget, eLegendSymbolStyle, xShapeFactory
2574 , rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol ));
2576 return xShape;
2579 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForPoint(
2580 const awt::Size& rEntryKeyAspectRatio
2581 , const VDataSeries& rSeries
2582 , sal_Int32 nPointIndex
2583 , const Reference< drawing::XShapes >& xTarget
2584 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2587 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2588 uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
2590 VLegendSymbolFactory::PropertyType ePropType =
2591 VLegendSymbolFactory::PropertyType::FilledSeries;
2593 // todo: maybe the property-style does not solely depend on the
2594 // legend-symbol type
2595 switch( eLegendSymbolStyle )
2597 case LegendSymbolStyle::Line:
2598 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2599 break;
2600 default:
2601 break;
2604 // the default properties for the data point are the data series properties.
2605 // If a data point has own attributes overwrite them
2606 Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
2607 Reference< beans::XPropertySet > xPointSet( xSeriesProps );
2608 if( rSeries.isAttributedDataPoint( nPointIndex ) )
2609 xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
2611 // if a data point has no own color use a color from the diagram's color scheme
2612 if( ! rSeries.hasPointOwnColor( nPointIndex ))
2614 Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
2615 if( xCloneable.is() && m_xColorScheme.is() )
2617 xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
2618 Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
2619 if( xChild.is())
2620 xChild->setParent( xSeriesProps );
2622 OSL_ASSERT( xPointSet.is());
2623 xPointSet->setPropertyValue(
2624 "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
2628 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2629 xTarget, eLegendSymbolStyle, xShapeFactory, xPointSet, ePropType, aExplicitSymbol ));
2631 return xShape;
2634 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
2635 const awt::Size& rEntryKeyAspectRatio
2636 , const VDataSeries& rSeries
2637 , const Reference< beans::XPropertySet >& xTextProperties
2638 , const Reference< drawing::XShapes >& xTarget
2639 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2640 , const Reference< uno::XComponentContext >& xContext
2643 std::vector< ViewLegendEntry > aResult;
2645 if( ! ( xShapeFactory.is() && xTarget.is() && xContext.is() ) )
2646 return aResult;
2650 ViewLegendEntry aEntry;
2651 OUString aLabelText;
2652 bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
2653 if( bVaryColorsByPoint )
2655 Sequence< OUString > aCategoryNames;
2656 if( m_pExplicitCategoriesProvider )
2657 aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
2659 for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
2661 // symbol
2662 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2664 // create the symbol
2665 Reference< drawing::XShape > xShape( createLegendSymbolForPoint( rEntryKeyAspectRatio,
2666 rSeries, nIdx, xSymbolGroup, xShapeFactory ) );
2668 // set CID to symbol for selection
2669 if( xShape.is() )
2671 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2673 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
2674 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2675 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2676 ShapeFactory::setShapeName( xShape, aCID );
2679 // label
2680 aLabelText = aCategoryNames[nIdx];
2681 if( xShape.is() || !aLabelText.isEmpty() )
2683 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2684 aResult.push_back(aEntry);
2688 else
2690 // symbol
2691 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2693 // create the symbol
2694 Reference< drawing::XShape > xShape( createLegendSymbolForSeries(
2695 rEntryKeyAspectRatio, rSeries, xSymbolGroup, xShapeFactory ) );
2697 // set CID to symbol for selection
2698 if( xShape.is())
2700 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2702 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2703 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2704 ShapeFactory::setShapeName( xShape, aCID );
2707 // label
2708 aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
2709 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2711 aResult.push_back(aEntry);
2714 // don't show legend entry of regression curve & friends if this type of chart
2715 // doesn't support statistics #i63016#, fdo#37197
2716 if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
2717 return aResult;
2719 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2720 if( xRegrCont.is())
2722 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves());
2723 sal_Int32 i = 0, nCount = aCurves.getLength();
2724 for( i=0; i<nCount; ++i )
2726 if( aCurves[i].is() )
2728 //label
2729 OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
2730 replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
2731 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
2733 // symbol
2734 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2736 // create the symbol
2737 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2738 xSymbolGroup, LegendSymbolStyle::Line, xShapeFactory,
2739 Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ),
2740 VLegendSymbolFactory::PropertyType::Line, uno::Any() ));
2742 // set CID to symbol for selection
2743 if( xShape.is())
2745 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2747 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
2748 ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
2749 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
2750 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2751 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2752 ShapeFactory::setShapeName( xShape, aCID );
2755 aResult.push_back(aEntry);
2760 catch( const uno::Exception & )
2762 DBG_UNHANDLED_EXCEPTION("chart2" );
2764 return aResult;
2767 VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
2768 const uno::Reference<XChartType>& xChartTypeModel
2769 , sal_Int32 nDimensionCount
2770 , bool bExcludingPositioning )
2772 if (!xChartTypeModel.is())
2773 return nullptr;
2775 OUString aChartType = xChartTypeModel->getChartType();
2777 VSeriesPlotter* pRet=nullptr;
2778 if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
2779 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2780 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
2781 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2782 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
2783 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
2784 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
2785 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
2786 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
2787 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2788 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
2789 pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
2790 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
2791 pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
2792 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
2793 pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
2794 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
2795 pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
2796 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
2797 pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
2798 else
2799 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2800 return pRet;
2803 } //namespace chart
2805 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */