summaryrefslogtreecommitdiffstats
path: root/chart2/source/view/charttypes/VSeriesPlotter.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'chart2/source/view/charttypes/VSeriesPlotter.cxx')
-rw-r--r--chart2/source/view/charttypes/VSeriesPlotter.cxx2847
1 files changed, 2847 insertions, 0 deletions
diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx
new file mode 100644
index 000000000..fd3a1fe1f
--- /dev/null
+++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx
@@ -0,0 +1,2847 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <VSeriesPlotter.hxx>
+#include <BaseGFXHelper.hxx>
+#include <VLineProperties.hxx>
+#include <ShapeFactory.hxx>
+#include <Diagram.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include <DataSeries.hxx>
+
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include <StatisticsHelper.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <Clipping.hxx>
+#include <servicenames_charttypes.hxx>
+#include <NumberFormatterWrapper.hxx>
+#include <DataSeriesHelper.hxx>
+#include <RegressionCurveModel.hxx>
+#include <RegressionCurveHelper.hxx>
+#include <VLegendSymbolFactory.hxx>
+#include <FormattedStringHelper.hxx>
+#include <RelativePositionHelper.hxx>
+#include <DateHelper.hxx>
+#include <DiagramHelper.hxx>
+#include <defines.hxx>
+#include <ChartModel.hxx>
+
+//only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
+#include "BarChart.hxx"
+#include "PieChart.hxx"
+#include "AreaChart.hxx"
+#include "CandleStickChart.hxx"
+#include "BubbleChart.hxx"
+#include "NetChart.hxx"
+#include <unonames.hxx>
+#include <SpecialCharacters.hxx>
+
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart/ErrorBarStyle.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/MovingAverageType.hpp>
+#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <o3tl/safeint.hxx>
+#include <tools/color.hxx>
+#include <tools/UnitConversion.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/math.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+
+#include <unotools/localedatawrapper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <functional>
+#include <map>
+#include <unordered_map>
+
+
+namespace chart {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart;
+using namespace ::com::sun::star::chart2;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Sequence;
+
+VDataSeriesGroup::CachedYValues::CachedYValues()
+ : m_bValuesDirty(true)
+ , m_fMinimumY(0.0)
+ , m_fMaximumY(0.0)
+{
+}
+
+VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
+ : m_aSeriesVector(1)
+ , m_bMaxPointCountDirty(true)
+ , m_nMaxPointCount(0)
+{
+ m_aSeriesVector[0] = std::move(pSeries);
+}
+
+VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
+ : m_aSeriesVector( std::move(other.m_aSeriesVector) )
+ , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
+ , m_nMaxPointCount( other.m_nMaxPointCount )
+ , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
+{
+}
+
+VDataSeriesGroup::~VDataSeriesGroup()
+{
+}
+
+void VDataSeriesGroup::deleteSeries()
+{
+ //delete all data series help objects:
+ m_aSeriesVector.clear();
+}
+
+void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
+{
+ m_aSeriesVector.push_back(std::move(pSeries));
+ m_bMaxPointCountDirty=true;
+}
+
+sal_Int32 VDataSeriesGroup::getSeriesCount() const
+{
+ return m_aSeriesVector.size();
+}
+
+VSeriesPlotter::VSeriesPlotter( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount, bool bCategoryXAxis )
+ : PlotterBase( nDimensionCount )
+ , m_pMainPosHelper( nullptr )
+ , m_xChartTypeModel(xChartTypeModel)
+ , m_bCategoryXAxis(bCategoryXAxis)
+ , m_nTimeResolution(css::chart::TimeUnit::DAY)
+ , m_aNullDate(30,12,1899)
+ , m_pExplicitCategoriesProvider(nullptr)
+ , m_bPointsWereSkipped(false)
+ , m_bPieLabelsAllowToMove(false)
+ , m_aAvailableOuterRect(0, 0, 0, 0)
+{
+ SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
+}
+
+VSeriesPlotter::~VSeriesPlotter()
+{
+ //delete all data series help objects:
+ for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup & rGroup : rGroupVector)
+ {
+ rGroup.deleteSeries();
+ }
+ rGroupVector.clear();
+ }
+ m_aZSlots.clear();
+
+ m_aSecondaryPosHelperMap.clear();
+
+ m_aSecondaryValueScales.clear();
+}
+
+void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ //take ownership of pSeries
+
+ OSL_PRECOND( pSeries, "series to add is NULL" );
+ if(!pSeries)
+ return;
+
+ if(m_bCategoryXAxis)
+ {
+ if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
+ pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ else
+ pSeries->setCategoryXAxis();
+ }
+ else
+ {
+ if( m_pExplicitCategoriesProvider )
+ pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ }
+
+ if(zSlot<0 || o3tl::make_unsigned(zSlot)>=m_aZSlots.size())
+ {
+ //new z slot
+ std::vector< VDataSeriesGroup > aZSlot;
+ aZSlot.emplace_back( std::move(pSeries) );
+ m_aZSlots.push_back( std::move(aZSlot) );
+ }
+ else
+ {
+ //existing zslot
+ std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
+
+ if(xSlot<0 || o3tl::make_unsigned(xSlot)>=rXSlots.size())
+ {
+ //append the series to already existing x series
+ rXSlots.emplace_back( std::move(pSeries) );
+ }
+ else
+ {
+ //x slot is already occupied
+ //y slot decides what to do:
+
+ VDataSeriesGroup& rYSlots = rXSlots[xSlot];
+ sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
+
+ if( ySlot < -1 )
+ {
+ //move all existing series in the xSlot to next slot
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ else if( ySlot == -1 || ySlot >= nYSlotCount)
+ {
+ //append the series to already existing y series
+ rYSlots.addSeries( std::move(pSeries) );
+ }
+ else
+ {
+ //y slot is already occupied
+ //insert at given y and x position
+
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ }
+ }
+}
+
+drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1.0,1.0,1.0);
+ if (!m_pPosHelper)
+ return aRet;
+
+ drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
+ aRet.DirectionZ = aScale.DirectionZ*0.2;
+ if(aRet.DirectionZ>1.0)
+ aRet.DirectionZ=1.0;
+ if(aRet.DirectionZ>10)
+ aRet.DirectionZ=10;
+ return aRet;
+}
+
+void VSeriesPlotter::releaseShapes()
+{
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ //iterate through all series in this x slot
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->releaseShapes();
+ }
+ }
+ }
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if( !pDataSeries->m_xGroupShape )
+ //create a group shape for this series and add to logic target:
+ pDataSeries->m_xGroupShape = createGroupShape( xTarget,pDataSeries->getCID() );
+ return pDataSeries->m_xGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xFrontSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //ensure that the back child is created first
+ getSeriesGroupShapeBackChild( pDataSeries, xTarget );
+ //use series group shape as parent for the new created front group shape
+ pDataSeries->m_xFrontSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xFrontSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xBackSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //use series group shape as parent for the new created back group shape
+ pDataSeries->m_xBackSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xBackSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget )
+{
+ //xTextTarget needs to be a 2D shape container always!
+ if(!rDataSeries.m_xLabelsGroupShape)
+ {
+ //create a 2D group shape for texts of this series and add to text target:
+ rDataSeries.m_xLabelsGroupShape = ShapeFactory::createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
+ }
+ return rDataSeries.m_xLabelsGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , bool bYError )
+{
+ rtl::Reference<SvxShapeGroupAnyD> &rShapeGroup =
+ bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
+
+ if(!rShapeGroup)
+ {
+ //create a group shape for this series and add to logic target:
+ rShapeGroup = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
+ }
+ return rShapeGroup;
+
+}
+
+OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , bool bAsPercentage )
+{
+ OUString aNumber;
+
+ if (m_apNumberFormatterWrapper)
+ {
+ sal_Int32 nNumberFormatKey = 0;
+ if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
+ nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
+ else if( bAsPercentage )
+ {
+ sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
+ if( nPercentFormat != -1 )
+ nNumberFormatKey = nPercentFormat;
+ }
+ else
+ {
+ nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
+ }
+ if(nNumberFormatKey<0)
+ nNumberFormatKey=0;
+
+ Color nLabelCol;
+ bool bColChanged;
+ aNumber = m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fValue, nLabelCol, bColChanged );
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ assert(aNumDecimalSep.getLength() > 0);
+ sal_Unicode cDecSeparator = aNumDecimalSep[0];
+ aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
+ , 3/*DecPlaces*/ , cDecSeparator );
+ }
+ return aNumber;
+}
+
+rtl::Reference<SvxShapeText> VSeriesPlotter::createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , VDataSeries& rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , double fSumValue
+ , const awt::Point& rScreenPosition2D
+ , LabelAlignment eAlignment
+ , sal_Int32 nOffset
+ , sal_Int32 nTextWidth )
+{
+ rtl::Reference<SvxShapeText> xTextShape;
+ Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
+
+ try
+ {
+ const uno::Reference< css::beans::XPropertySet >& xPropertySet(
+ rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xPropertySet.is() )
+ {
+ uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
+ if( aAny.hasValue() )
+ {
+ aAny >>= aCustomLabels;
+ }
+ }
+
+ awt::Point aScreenPosition2D(rScreenPosition2D);
+ if(eAlignment==LABEL_ALIGN_LEFT)
+ aScreenPosition2D.X -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_RIGHT)
+ aScreenPosition2D.X += nOffset;
+ else if(eAlignment==LABEL_ALIGN_TOP)
+ aScreenPosition2D.Y -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_BOTTOM)
+ aScreenPosition2D.Y += nOffset;
+
+ rtl::Reference<SvxShapeGroup> xTarget_ =
+ ShapeFactory::createGroup2D(
+ getLabelsGroupShape(rDataSeries, xTarget),
+ ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
+
+ //check whether the label needs to be created and how:
+ DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
+
+ if( !pLabel )
+ return xTextShape;
+
+ //prepare legend symbol
+
+ // get the font size for the label through the "CharHeight" property
+ // attached to the passed data series entry.
+ // (By tracing font height values it results that for pie chart the
+ // font size is not the same for all labels, but here no font size
+ // modification occurs).
+ float fViewFontSize( 10.0 );
+ {
+ uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xProps.is() )
+ xProps->getPropertyValue( "CharHeight") >>= fViewFontSize;
+ fViewFontSize = convertPointToMm100(fViewFontSize);
+ }
+
+ // the font height is used for computing the size of an optional legend
+ // symbol to be prepended to the text label.
+ rtl::Reference< SvxShapeGroup > xSymbol;
+ if(pLabel->ShowLegendSymbol)
+ {
+ sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
+ awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
+ sal_Int32 nSymbolWidth = aCurrentRatio.Width;
+ if( aCurrentRatio.Height > 0 )
+ {
+ nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
+ }
+ awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
+
+ if( rDataSeries.isVaryColorsByPoint() )
+ xSymbol = VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_ );
+ else
+ xSymbol = VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_ );
+ }
+
+ //prepare text
+ bool bTextWrap = false;
+ OUString aSeparator(" ");
+ double fRotationDegrees = 0.0;
+ try
+ {
+ uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if(xPointProps.is())
+ {
+ xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
+ xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
+ // Extract the optional text rotation through the
+ // "TextRotation" property attached to the passed data point.
+ xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ sal_Int32 nLineCountForSymbolsize = 0;
+ sal_uInt32 nTextListLength = 4;
+ sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
+ Sequence< OUString > aTextList( nTextListLength );
+
+ bool bUseCustomLabel = nCustomLabelsCount > 0;
+ if( bUseCustomLabel )
+ {
+ nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
+ aSeparator = "";
+ aTextList = Sequence< OUString >( nTextListLength );
+ auto pTextList = aTextList.getArray();
+ for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
+ {
+ switch( aCustomLabels[i]->getFieldType() )
+ {
+ case DataPointCustomLabelFieldType_VALUE:
+ {
+ pTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
+ break;
+ }
+ case DataPointCustomLabelFieldType_CATEGORYNAME:
+ {
+ pTextList[i] = getCategoryName( nPointIndex );
+ break;
+ }
+ case DataPointCustomLabelFieldType_SERIESNAME:
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ break;
+ }
+ case DataPointCustomLabelFieldType_PERCENTAGE:
+ {
+ if(fSumValue == 0.0)
+ fSumValue = 1.0;
+ fValue /= fSumValue;
+ if(fValue < 0)
+ fValue *= -1.0;
+
+ pTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLRANGE:
+ {
+ if (aCustomLabels[i]->getDataLabelsRange())
+ pTextList[i] = aCustomLabels[i]->getString();
+ else
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLREF:
+ {
+ // TODO: for now doesn't show placeholder
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_TEXT:
+ {
+ pTextList[i] = aCustomLabels[i]->getString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_NEWLINE:
+ {
+ pTextList[i] = "\n";
+ break;
+ }
+ default:
+ break;
+ }
+ aCustomLabels[i]->setString( aTextList[i] );
+ }
+ }
+ else
+ {
+ auto pTextList = aTextList.getArray();
+ if( pLabel->ShowCategoryName )
+ {
+ pTextList[0] = getCategoryName( nPointIndex );
+ }
+
+ if( pLabel->ShowSeriesName )
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[1] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ }
+
+ if( pLabel->ShowNumber )
+ {
+ pTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
+ }
+
+ if( pLabel->ShowNumberInPercent )
+ {
+ if(fSumValue==0.0)
+ fSumValue=1.0;
+ fValue /= fSumValue;
+ if( fValue < 0 )
+ fValue*=-1.0;
+
+ pTextList[3] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ }
+ }
+
+ for( auto const & line : std::as_const(aTextList) )
+ {
+ if( !line.isEmpty() )
+ {
+ ++nLineCountForSymbolsize;
+ }
+ }
+
+ //prepare properties for multipropertyset-interface of shape
+ tNameSequence* pPropNames;
+ tAnySequence* pPropValues;
+ if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
+ return xTextShape;
+
+ // set text alignment for the text shape to be created.
+ LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
+
+ // check if data series entry percent value and absolute value have to
+ // be appended to the text label, and what should be the separator
+ // character (comma, space, new line). In case it is a new line we get
+ // a multi-line label.
+ bool bMultiLineLabel = ( aSeparator == "\n" );
+
+ if( bUseCustomLabel )
+ {
+ Sequence< uno::Reference< XFormattedString > > aFormattedLabels(
+ comphelper::containerToSequence<uno::Reference<XFormattedString>>(aCustomLabels));
+
+ // create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+ else
+ {
+ // join text list elements
+ OUStringBuffer aText;
+ for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
+ {
+ if( !aTextList[nN].isEmpty() )
+ {
+ if( !aText.isEmpty() )
+ {
+ aText.append(aSeparator);
+ }
+ aText.append( aTextList[nN] );
+ }
+ }
+
+ //create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+
+ if( !xTextShape.is() )
+ return xTextShape;
+
+ // we need to use a default value for the maximum width property ?
+ if( nTextWidth == 0 && bTextWrap )
+ {
+ sal_Int32 nMinSize =
+ (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
+ ? m_aPageReferenceSize.Height
+ : m_aPageReferenceSize.Width;
+ nTextWidth = nMinSize / 3;
+ }
+
+ // in case text must be wrapped set the maximum width property
+ // for the text shape
+ if( nTextWidth != 0 && bTextWrap )
+ {
+ // compute the height of a line of text
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ {
+ nLineCountForSymbolsize = 1;
+ }
+ awt::Size aTextSize = xTextShape->getSize();
+ sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
+
+ // set maximum text width
+ uno::Any aTextMaximumFrameWidth( nTextWidth );
+ xTextShape->SvxShape::setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
+
+ // compute the total lines of text
+ aTextSize = xTextShape->getSize();
+ nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
+ }
+
+ // in case text is rotated, the transformation property of the text
+ // shape is modified.
+ if( fRotationDegrees != 0.0 )
+ {
+ const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
+ xTextShape->SvxShape::setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
+ LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
+ }
+
+ awt::Point aTextShapePos(xTextShape->getPosition());
+ if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
+ {
+ awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
+ if( aRelPos.X != -1 )
+ {
+ xTextShape->setPosition(aRelPos);
+ if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
+ rDataSeries.getPropertiesOfSeries()->getPropertyValue( "ShowCustomLeaderLines" ).get<sal_Bool>())
+ {
+ sal_Int32 nX1 = rScreenPosition2D.X;
+ sal_Int32 nY1 = rScreenPosition2D.Y;
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ ::basegfx::B2IRectangle aRect(BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
+ if (nX1 < aRelPos.X)
+ nX2 = aRelPos.X;
+ else if (nX1 > aRect.getMaxX())
+ nX2 = aRect.getMaxX();
+
+ if (nY1 < aRect.getMinY())
+ nY2 = aRect.getMinY();
+ else if (nY1 > aRect.getMaxY())
+ nY2 = aRect.getMaxY();
+
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
+ double fPageDiagonaleLength
+ = std::hypot(m_aPageReferenceSize.Width, m_aPageReferenceSize.Height);
+ if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
+ {
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ VLineProperties aVLineProperties;
+ ShapeFactory::createLine2D(xTarget, aPoints, &aVLineProperties);
+ }
+ }
+ }
+ }
+
+ // in case legend symbol has to be displayed, text shape position is
+ // slightly changed.
+ const awt::Point aUnrotatedTextPos(xTextShape->getPosition());
+ if( xSymbol.is() )
+ {
+ const awt::Point aOldTextPos( xTextShape->getPosition() );
+ awt::Point aNewTextPos( aOldTextPos );
+
+ awt::Point aSymbolPosition( aUnrotatedTextPos );
+ awt::Size aSymbolSize( xSymbol->getSize() );
+ awt::Size aTextSize = xTextShape->getSize();
+
+ sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ nLineCountForSymbolsize = 1;
+ aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
+
+ if(eAlignment==LABEL_ALIGN_LEFT
+ || eAlignment==LABEL_ALIGN_LEFT_TOP
+ || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
+ {
+ aSymbolPosition.X -= nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_RIGHT
+ || eAlignment==LABEL_ALIGN_RIGHT_TOP
+ || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
+ {
+ aNewTextPos.X += nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_TOP
+ || eAlignment==LABEL_ALIGN_BOTTOM
+ || eAlignment==LABEL_ALIGN_CENTER )
+ {
+ aSymbolPosition.X -= nXDiff/2;
+ aNewTextPos.X += nXDiff/2;
+ }
+
+ xSymbol->setPosition( aSymbolPosition );
+ xTextShape->setPosition( aNewTextPos );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return xTextShape;
+}
+
+namespace
+{
+double lcl_getErrorBarLogicLength(
+ const uno::Sequence< double > & rData,
+ const uno::Reference< beans::XPropertySet >& xProp,
+ sal_Int32 nErrorBarStyle,
+ sal_Int32 nIndex,
+ bool bPositive,
+ bool bYError )
+{
+ double fResult = std::numeric_limits<double>::quiet_NaN();
+ try
+ {
+ switch( nErrorBarStyle )
+ {
+ case css::chart::ErrorBarStyle::NONE:
+ break;
+ case css::chart::ErrorBarStyle::VARIANCE:
+ fResult = StatisticsHelper::getVariance( rData );
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
+ fResult = StatisticsHelper::getStandardDeviation( rData );
+ break;
+ case css::chart::ErrorBarStyle::RELATIVE:
+ {
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ if( nIndex >=0 && nIndex < rData.getLength() &&
+ ! std::isnan( rData[nIndex] ) &&
+ ! std::isnan( fPercent ))
+ {
+ fResult = rData[nIndex] * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::ABSOLUTE:
+ xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fResult;
+ break;
+ case css::chart::ErrorBarStyle::ERROR_MARGIN:
+ {
+ // todo: check if this is really what's called error-margin
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ double fMaxValue = -std::numeric_limits<double>::infinity();
+ for(double d : rData)
+ {
+ if(fMaxValue < d)
+ fMaxValue = d;
+ }
+ if( std::isfinite( fMaxValue ) &&
+ std::isfinite( fPercent ))
+ {
+ fResult = fMaxValue * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_ERROR:
+ fResult = StatisticsHelper::getStandardError( rData );
+ break;
+ case css::chart::ErrorBarStyle::FROM_DATA:
+ {
+ uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
+ if( xErrorBarData.is())
+ fResult = StatisticsHelper::getErrorFromDataSource(
+ xErrorBarData, nIndex, bPositive, bYError);
+ }
+ break;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return fResult;
+}
+
+void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
+ , std::vector<std::vector<css::drawing::Position3D>>& rPoly, sal_Int32 nSequenceIndex )
+{
+ double fFixedWidth = 200.0;
+
+ aMainDirection.normalize();
+ ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
+ aOrthoDirection.normalize();
+
+ ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
+ ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
+ ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
+
+ AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
+ AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
+}
+
+::basegfx::B2DVector lcl_getErrorBarMainDirection(
+ const drawing::Position3D& rStart
+ , const drawing::Position3D& rBottomEnd
+ , PlottingPositionHelper const * pPosHelper
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , bool bYError )
+{
+ ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
+ , rStart.PositionY - rBottomEnd.PositionY );
+ if( !aMainDirection.getLength() )
+ {
+ //get logic clip values:
+ double MinX = pPosHelper->getLogicMinX();
+ double MinY = pPosHelper->getLogicMinY();
+ double MaxX = pPosHelper->getLogicMaxX();
+ double MaxY = pPosHelper->getLogicMaxY();
+ double fZ = pPosHelper->getLogicMinZ();
+
+ if( bYError )
+ {
+ //main direction has constant x value
+ MinX = rUnscaledLogicPosition.PositionX;
+ MaxX = rUnscaledLogicPosition.PositionX;
+ }
+ else
+ {
+ //main direction has constant y value
+ MinY = rUnscaledLogicPosition.PositionY;
+ MaxY = rUnscaledLogicPosition.PositionY;
+ }
+
+ drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
+ drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
+
+ aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
+ , aStart.PositionY - aEnd.PositionY );
+ }
+ if( !aMainDirection.getLength() )
+ {
+ //@todo
+ }
+ return aMainDirection;
+}
+
+drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
+ , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
+{
+ if(!pPosHelper)
+ return drawing::Position3D(0,0,0);
+ pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
+ pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
+ return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
+}
+
+} // anonymous namespace
+
+void VSeriesPlotter::createErrorBar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
+ , const VDataSeries& rVDataSeries
+ , sal_Int32 nIndex
+ , bool bYError /* = true */
+ , const double* pfScaledLogicX
+ )
+{
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ if( ! xErrorBarProperties.is())
+ return;
+
+ try
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
+
+ xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
+ xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
+ xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
+ {
+ if (bYError)
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+ else
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ }
+
+ bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
+ bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
+ drawing::Position3D aMiddle(aUnscaledLogicPosition);
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ if( pfScaledLogicX )
+ fScaledX = *pfScaledLogicX;
+ else
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
+
+ drawing::Position3D aNegative(aMiddle);
+ drawing::Position3D aPositive(aMiddle);
+
+ uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
+
+ if( bShowPositive )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY+=fLength;
+ aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX+=fLength;
+ aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
+ }
+ else
+ bShowPositive = false;
+ }
+
+ if( bShowNegative )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY-=fLength;
+ aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX-=fLength;
+ aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ if (std::isfinite(aNegative.PositionX) &&
+ std::isfinite(aNegative.PositionY) &&
+ std::isfinite(aNegative.PositionZ)) {
+ bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
+ } else {
+ // If error bars result in a numerical problem (e.g., an
+ // error bar on a logarithmic chart that results in a point
+ // <= 0) then just turn off the error bar.
+ //
+ // TODO: This perhaps should display a warning, so the user
+ // knows why a bar is not appearing.
+ // TODO: This test could also be added to the positive case,
+ // though a numerical overflow there is less likely.
+ bShowNegative = false;
+ }
+ }
+ else
+ bShowNegative = false;
+ }
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+
+ sal_Int32 nSequenceIndex=0;
+ if( bShowNegative )
+ AddPointToPoly( aPoly, aNegative, nSequenceIndex );
+ AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
+ if( bShowPositive )
+ AddPointToPoly( aPoly, aPositive, nSequenceIndex );
+
+ if( bShowNegative && bCreateNegativeBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
+ }
+ if( bShowPositive && bCreatePositiveBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
+ }
+
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( xTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+}
+
+void VSeriesPlotter::addErrorBorder(
+ const drawing::Position3D& rPos0
+ ,const drawing::Position3D& rPos1
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
+{
+ std::vector<std::vector<css::drawing::Position3D>> aPoly { { rPos0, rPos1} };
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ rTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, rErrorBorderProp,
+ PropertyMapper::getPropertyNameMapForLineProperties() );
+}
+
+void VSeriesPlotter::createErrorRectangle(
+ const drawing::Position3D& rUnscaledLogicPosition
+ ,VDataSeries& rVDataSeries
+ ,sal_Int32 nIndex
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,bool bUseXErrorData
+ ,bool bUseYErrorData )
+{
+ if ( m_nDimension != 2 )
+ return;
+
+ // error border properties
+ Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropX.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesX =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, false );
+
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropY.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesY =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, true );
+
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ try
+ {
+ bool bShowXPositive = false;
+ bool bShowXNegative = false;
+ bool bShowYPositive = false;
+ bool bShowYNegative = false;
+
+ sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
+ sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
+
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
+ xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
+ xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
+ }
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
+ xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
+ xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
+ }
+
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
+ bUseXErrorData = false;
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
+ bUseYErrorData = false;
+
+ if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
+ return;
+
+ if ( !m_pPosHelper )
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
+ uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
+
+ double fPosX = 0.0;
+ double fPosY = 0.0;
+ double fNegX = 0.0;
+ double fNegY = 0.0;
+ if ( bUseXErrorData )
+ {
+ if ( bShowXPositive )
+ fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, true, false );
+ if ( bShowXNegative )
+ fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, false, false );
+ }
+
+ if ( bUseYErrorData )
+ {
+ if ( bShowYPositive )
+ fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, true, true );
+ if ( bShowYNegative )
+ fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, false, true );
+ }
+
+ if ( !( std::isfinite( fPosX ) &&
+ std::isfinite( fPosY ) &&
+ std::isfinite( fNegX ) &&
+ std::isfinite( fNegY ) ) )
+ return;
+
+ drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY - fNegY, fZ ) );
+ drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY + fPosY, fZ ) );
+ drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY + fPosY, fZ ) );
+ drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY - fNegY, fZ ) );
+ if ( bUseXErrorData )
+ {
+ // top border
+ addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
+
+ // bottom border
+ addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
+ }
+
+ if ( bUseYErrorData )
+ {
+ // left border
+ addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
+
+ // right border
+ addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
+ }
+}
+
+void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, false);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , false /* bYError */
+ , nullptr );
+ }
+}
+
+void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double const * pfScaledLogicX )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, true);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , true /* bYError */
+ , pfScaledLogicX );
+ }
+}
+
+void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ bool bMaySkipPoints )
+{
+ if(m_nDimension!=2)
+ return;
+ rtl::Reference< DataSeries > xContainer( rVDataSeries.getModel() );
+ if(!xContainer.is())
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ const std::vector< rtl::Reference< ::chart::RegressionCurveModel > > & aCurveList = xContainer->getRegressionCurves2();
+
+ for(std::size_t nN=0; nN<aCurveList.size(); nN++)
+ {
+ const auto & rCurve = aCurveList[nN];
+ uno::Reference< XRegressionCurveCalculator > xCalculator( rCurve->getCalculator() );
+ if( !xCalculator.is())
+ continue;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( rCurve );
+
+ sal_Int32 aDegree = 2;
+ sal_Int32 aPeriod = 2;
+ sal_Int32 aMovingAverageType = css::chart2::MovingAverageType::Prior;
+ double aExtrapolateForward = 0.0;
+ double aExtrapolateBackward = 0.0;
+ bool bForceIntercept = false;
+ double aInterceptValue = 0.0;
+
+ if ( !bAverageLine )
+ {
+ rCurve->getPropertyValue( "PolynomialDegree") >>= aDegree;
+ rCurve->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
+ rCurve->getPropertyValue( "MovingAverageType") >>= aMovingAverageType;
+ rCurve->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
+ rCurve->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
+ rCurve->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
+ if (bForceIntercept)
+ rCurve->getPropertyValue( "InterceptValue") >>= aInterceptValue;
+ }
+
+ double fChartMinX = m_pPosHelper->getLogicMinX();
+ double fChartMaxX = m_pPosHelper->getLogicMaxX();
+
+ double fMinX = fChartMinX;
+ double fMaxX = fChartMaxX;
+
+ double fPointScale = 1.0;
+
+ if( !bAverageLine )
+ {
+ rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
+ fMaxX += aExtrapolateForward;
+ fMinX -= aExtrapolateBackward;
+
+ fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
+ // sanitize the value, tdf#119922
+ fPointScale = std::min(fPointScale, 1000.0);
+ }
+
+ xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod,
+ aMovingAverageType);
+ xCalculator->recalculateRegression(rVDataSeries.getAllX(), rVDataSeries.getAllY());
+ sal_Int32 nPointCount = 100 * fPointScale;
+
+ if ( nPointCount < 2 )
+ nPointCount = 2;
+
+ std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
+ uno::Reference< chart2::XScaling > xScalingX;
+ uno::Reference< chart2::XScaling > xScalingY;
+ if( aScales.size() >= 2 )
+ {
+ xScalingX.set( aScales[0].Scaling );
+ xScalingY.set( aScales[1].Scaling );
+ }
+
+ const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
+ xCalculator->getCurveValues(
+ fMinX, fMaxX, nPointCount,
+ xScalingX, xScalingY, bMaySkipPoints ));
+
+ nPointCount = aCalculatedPoints.getLength();
+
+ drawing::PolyPolygonShape3D aRegressionPoly;
+ aRegressionPoly.SequenceX.realloc(1);
+ aRegressionPoly.SequenceY.realloc(1);
+ aRegressionPoly.SequenceZ.realloc(1);
+ auto pSequenceX = aRegressionPoly.SequenceX.getArray();
+ auto pSequenceY = aRegressionPoly.SequenceY.getArray();
+ auto pSequenceZ = aRegressionPoly.SequenceZ.getArray();
+ pSequenceX[0].realloc(nPointCount);
+ pSequenceY[0].realloc(nPointCount);
+ auto pSequenceX0 = pSequenceX[0].getArray();
+ auto pSequenceY0 = pSequenceY[0].getArray();
+
+ sal_Int32 nRealPointCount = 0;
+
+ for(geometry::RealPoint2D const & p : aCalculatedPoints)
+ {
+ double fLogicX = p.X;
+ double fLogicY = p.Y;
+ double fLogicZ = 0.0; //dummy
+
+ // fdo#51656: don't scale mean value lines
+ if(!bAverageLine)
+ m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
+
+ if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
+ !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
+ !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
+ {
+ pSequenceX0[nRealPointCount] = fLogicX;
+ pSequenceY0[nRealPointCount] = fLogicY;
+ nRealPointCount++;
+ }
+ }
+ pSequenceX[0].realloc(nRealPointCount);
+ pSequenceY[0].realloc(nRealPointCount);
+ pSequenceZ[0].realloc(nRealPointCount);
+
+ drawing::PolyPolygonShape3D aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
+ aRegressionPoly = aClippedPoly;
+ m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
+
+ awt::Point aDefaultPos;
+ if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
+ {
+ VLineProperties aVLineProperties;
+ aVLineProperties.initFromPropertySet( rCurve );
+
+ //create an extra group shape for each curve for selection handling
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionGroupShapes =
+ createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
+ ShapeFactory::setShapeName( xShape, "MarkHandles" );
+ aDefaultPos = xShape->getPosition();
+ }
+
+ // curve equation and correlation coefficient
+ uno::Reference< beans::XPropertySet > xEquationProperties( rCurve->getEquationProperties());
+ if( xEquationProperties.is())
+ {
+ createRegressionCurveEquationShapes(
+ rVDataSeries.getDataCurveEquationCID( nN ),
+ xEquationProperties, xEquationTarget, xCalculator,
+ aDefaultPos );
+ }
+ }
+}
+
+static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
+{
+ const sal_Int32 nStringLength = aString.getLength();
+ sal_Int32 nMaxLineLength = 0;
+
+ for ( sal_Int32 i=0; i<nStringLength; i++ )
+ {
+ sal_Int32 indexSep = aString.indexOf( "\n", i );
+ if ( indexSep < 0 )
+ indexSep = nStringLength;
+ sal_Int32 nLineLength = indexSep - i;
+ if ( nLineLength > nMaxLineLength )
+ nMaxLineLength = nLineLength;
+ i = indexSep;
+ }
+
+ return nMaxLineLength;
+}
+
+void VSeriesPlotter::createRegressionCurveEquationShapes(
+ const OUString & rEquationCID,
+ const uno::Reference< beans::XPropertySet > & xEquationProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
+ awt::Point aDefaultPos )
+{
+ OSL_ASSERT( xEquationProperties.is());
+ if( !xEquationProperties.is())
+ return;
+
+ bool bShowEquation = false;
+ bool bShowCorrCoeff = false;
+ if(!(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
+ ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff )))
+ return;
+
+ if( ! (bShowEquation || bShowCorrCoeff))
+ return;
+
+ OUStringBuffer aFormula;
+ sal_Int32 nNumberFormatKey = 0;
+ sal_Int32 nFormulaWidth = 0;
+ xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
+ bool bResizeEquation = true;
+ sal_Int32 nMaxIteration = 2;
+ if ( bShowEquation )
+ {
+ OUString aXName, aYName;
+ if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
+ aXName = OUString( "x" );
+ if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
+ aYName = OUString( "f(x)" );
+ xRegressionCurveCalculator->setXYNames( aXName, aYName );
+ }
+
+ for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
+ {
+ bResizeEquation = false;
+ if( bShowEquation )
+ {
+ if (m_apNumberFormatterWrapper)
+ { // iteration 0: default representation (no wrap)
+ // iteration 1: expected width (nFormulaWidth) is calculated
+ aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
+ m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
+ nNumberFormatKey, nFormulaWidth );
+ nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
+ }
+ else
+ {
+ aFormula = xRegressionCurveCalculator->getRepresentation();
+ }
+
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "\n" );
+ }
+ }
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "R" + OUStringChar( aSuperscriptFigures[2] ) + " = " );
+ double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
+ if (m_apNumberFormatterWrapper)
+ {
+ Color nLabelCol;
+ bool bColChanged;
+ aFormula.append(
+ m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ sal_Unicode aDecimalSep = aNumDecimalSep[0];
+ aFormula.append( ::rtl::math::doubleToUString(
+ fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
+ }
+ }
+
+ awt::Point aScreenPosition2D;
+ chart2::RelativePosition aRelativePosition;
+ if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
+ {
+ //@todo decide whether x is primary or secondary
+ double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
+ double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
+ aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
+ aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
+ }
+ else
+ aScreenPosition2D = aDefaultPos;
+
+ if( !aFormula.isEmpty())
+ {
+ // set fill and line properties on creation
+ tNameSequence aNames;
+ tAnySequence aValues;
+ PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
+
+ rtl::Reference<SvxShapeText> xTextShape = ShapeFactory::createText(
+ xEquationTarget, aFormula.makeStringAndClear(),
+ aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
+
+ ShapeFactory::setShapeName( xTextShape, rEquationCID );
+ awt::Size aSize( xTextShape->getSize() );
+ awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
+ aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
+ //ensure that the equation is fully placed within the page (if possible)
+ if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
+ aPos.X = m_aPageReferenceSize.Width - aSize.Width;
+ if( aPos.X < 0 )
+ {
+ aPos.X = 0;
+ if ( nFormulaWidth > 0 )
+ {
+ bResizeEquation = true;
+ if ( nCountIteration < nMaxIteration-1 )
+ xEquationTarget->remove( xTextShape ); // remove equation
+ nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
+ nFormulaWidth -= nCountIteration;
+ if ( nFormulaWidth < 0 )
+ nFormulaWidth = 0;
+ }
+ }
+ if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
+ aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
+ if( aPos.Y < 0 )
+ aPos.Y = 0;
+ if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
+ xTextShape->setPosition(aPos); // if equation was not removed
+ }
+ }
+}
+
+void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
+{
+ m_nTimeResolution = TimeResolution;
+ m_aNullDate = rNullDate;
+}
+
+// MinimumAndMaximumSupplier
+tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
+{
+ tools::Long nRet = css::chart::TimeUnit::YEAR;
+ if (!m_pExplicitCategoriesProvider)
+ return nRet;
+
+ const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
+ if (rDateCategories.empty())
+ return nRet;
+
+ std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
+
+ aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
+ if (aIt == aEnd)
+ return nRet;
+
+ Date aNullDate(30,12,1899);
+ if (m_apNumberFormatterWrapper)
+ aNullDate = m_apNumberFormatterWrapper->getNullDate();
+
+ Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
+ ++aIt;
+ for(;aIt!=aEnd;++aIt)
+ {
+ if (std::isnan(*aIt))
+ continue;
+
+ Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
+ if( nRet == css::chart::TimeUnit::YEAR )
+ {
+ if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::MONTH;
+ }
+ if( nRet == css::chart::TimeUnit::MONTH )
+ {
+ if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::DAY;
+ }
+ if( nRet == css::chart::TimeUnit::DAY )
+ break;
+ aPrevious=aCurrent;
+ }
+
+ return nRet;
+}
+double VSeriesPlotter::getMinimumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMinimum;
+}
+double VSeriesPlotter::getMaximumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMinY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMinimum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMinimum;
+}
+
+double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMaxY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for( std::vector< VDataSeriesGroup > & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMaximum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumZ()
+{
+ //this is the default for all charts without a meaningful z axis
+ return 1.0;
+}
+double VSeriesPlotter::getMaximumZ()
+{
+ if( m_nDimension!=3 || m_aZSlots.empty() )
+ return getMinimumZ()+1;
+ return m_aZSlots.size();
+}
+
+namespace
+{
+ bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
+ {
+ // default implementation: true for Y axes, and for value X axis
+ if( nDimensionIndex == 0 )
+ return !bCategoryXAxis;
+ return nDimensionIndex == 1;
+ }
+}
+
+bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
+{
+ return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
+{
+ // do not expand axes in 3D charts
+ return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
+ rfMinimum = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
+ rfMaximum = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::infinity();
+ rfMaxY = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
+ rfMinY = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
+ rfMaxY = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinY))
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaxY))
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+}
+
+sal_Int32 VSeriesPlotter::getPointCount() const
+{
+ sal_Int32 nRet = 0;
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ sal_Int32 nPointCount = XSlot.getPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ }
+ return nRet;
+}
+
+void VSeriesPlotter::setNumberFormatsSupplier(
+ const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
+{
+ m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
+}
+
+void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
+{
+ m_xColorScheme = xColorScheme;
+}
+
+void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
+{
+ m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
+}
+
+sal_Int32 VDataSeriesGroup::getPointCount() const
+{
+ if(!m_bMaxPointCountDirty)
+ return m_nMaxPointCount;
+
+ sal_Int32 nRet = 0;
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ m_nMaxPointCount=nRet;
+ m_aListOfCachedYValues.clear();
+ m_aListOfCachedYValues.resize(m_nMaxPointCount);
+ m_bMaxPointCountDirty=false;
+ return nRet;
+}
+
+sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
+{
+ sal_Int32 nRet = 0;
+
+ if (!m_aSeriesVector.empty())
+ nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
+
+ return nRet;
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ for(sal_Int32 nN=0;nN<nPointCount;nN++)
+ {
+ double fX = pSeries->getXValue( nN );
+ if( std::isnan(fX) )
+ continue;
+ if(rfMaximum<fX)
+ rfMaximum=fX;
+ if(rfMinimum>fX)
+ rfMinimum=fX;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+namespace {
+
+/**
+ * Keep track of minimum and maximum Y values for one or more data series.
+ * When multiple data series exist, that indicates that the data series are
+ * stacked.
+ *
+ * <p>For each X value, we calculate separate Y value ranges for each data
+ * series in the first pass. In the second pass, we calculate the minimum Y
+ * value by taking the absolute minimum value of all data series, whereas
+ * the maximum Y value is the sum of all the series maximum Y values.</p>
+ *
+ * <p>Once that's done for all X values, the final min / max Y values get
+ * calculated by taking the absolute min / max Y values across all the X
+ * values.</p>
+ */
+class PerXMinMaxCalculator
+{
+ typedef std::pair<double, double> MinMaxType;
+ typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
+ typedef std::map<double, SeriesMinMaxType> GroupMinMaxType;
+ typedef std::unordered_map<double, MinMaxType> TotalStoreType;
+ GroupMinMaxType m_SeriesGroup;
+ size_t mnCurSeries;
+
+public:
+ PerXMinMaxCalculator() : mnCurSeries(0) {}
+
+ void nextSeries() { ++mnCurSeries; }
+
+ void setValue(double fX, double fY)
+ {
+ SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
+ if (!pStore)
+ // This shouldn't happen!
+ return;
+
+ SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
+ if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
+ {
+ MinMaxType& r = it->second;
+ // A min-max pair already exists for this series. Update it.
+ if (fY < r.first)
+ r.first = fY;
+ if (r.second < fY)
+ r.second = fY;
+ }
+ else
+ {
+ // No existing pair. Insert a new one.
+ pStore->insert(
+ it, SeriesMinMaxType::value_type(
+ mnCurSeries, MinMaxType(fY,fY)));
+ }
+ }
+
+ void getTotalRange(double& rfMin, double& rfMax) const
+ {
+ TotalStoreType aStore;
+ getTotalStore(aStore);
+
+ if (aStore.empty())
+ {
+ rfMin = std::numeric_limits<double>::quiet_NaN();
+ rfMax = std::numeric_limits<double>::quiet_NaN();
+ return;
+ }
+
+ TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
+ rfMin = it->second.first;
+ rfMax = it->second.second;
+ for (++it; it != itEnd; ++it)
+ {
+ if (rfMin > it->second.first)
+ rfMin = it->second.first;
+ if (rfMax < it->second.second)
+ rfMax = it->second.second;
+ }
+ }
+
+private:
+ /**
+ * Parse all data and reduce them into a set of global Y value ranges per
+ * X value.
+ */
+ void getTotalStore(TotalStoreType& rStore) const
+ {
+ TotalStoreType aStore;
+ for (auto const& it : m_SeriesGroup)
+ {
+ double fX = it.first;
+
+ const SeriesMinMaxType& rSeries = it.second;
+ for (auto const& series : rSeries)
+ {
+ double fYMin = series.second.first, fYMax = series.second.second;
+ TotalStoreType::iterator itr = aStore.find(fX);
+ if (itr == aStore.end())
+ // New min-max pair for give X value.
+ aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
+ else
+ {
+ MinMaxType& r = itr->second;
+ if (fYMin < r.first)
+ r.first = fYMin; // min y-value
+
+ r.second += fYMax; // accumulative max y-value.
+ }
+ }
+ }
+ rStore.swap(aStore);
+ }
+
+ SeriesMinMaxType* getByXValue(double fX)
+ {
+ GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
+ if (it == m_SeriesGroup.end())
+ {
+ std::pair<GroupMinMaxType::iterator,bool> r =
+ m_SeriesGroup.insert(std::make_pair(fX, SeriesMinMaxType{}));
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+ }
+
+ return &it->second;
+ }
+};
+
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
+ double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+
+ if (m_aSeriesVector.empty())
+ // No data series. Bail out.
+ return;
+
+ PerXMinMaxCalculator aRangeCalc;
+ for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
+ {
+ if (nAxisIndex != pSeries->getAttachedAxisIndex())
+ continue;
+
+ double fX = pSeries->getXValue(i);
+ if (std::isnan(fX))
+ continue;
+
+ if (fX < fMinX || fX > fMaxX)
+ // Outside specified X range. Skip it.
+ continue;
+
+ double fY = pSeries->getYValue(i);
+ if (std::isnan(fY))
+ continue;
+
+ aRangeCalc.setValue(fX, fY);
+ }
+ aRangeCalc.nextSeries();
+ }
+
+ aRangeCalc.getTotalRange(rfMinY, rfMaxY);
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const
+{
+ assert(nCategoryIndex >= 0);
+ assert(nCategoryIndex < getPointCount());
+
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ if(m_aSeriesVector.empty())
+ return;
+
+ CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
+ if( !aCachedYValues.m_bValuesDirty )
+ {
+ //return cached values
+ rfMinimumY = aCachedYValues.m_fMinimumY;
+ rfMaximumY = aCachedYValues.m_fMaximumY;
+ return;
+ }
+
+ double fTotalSum = std::numeric_limits<double>::quiet_NaN();
+ double fPositiveSum = std::numeric_limits<double>::quiet_NaN();
+ double fNegativeSum = std::numeric_limits<double>::quiet_NaN();
+ double fFirstPositiveY = std::numeric_limits<double>::quiet_NaN();
+ double fFirstNegativeY = std::numeric_limits<double>::quiet_NaN();
+
+ if( bSeparateStackingForDifferentSigns )
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( fValueMaxY >= 0 )
+ {
+ if( std::isnan( fPositiveSum ) )
+ fPositiveSum = fFirstPositiveY = fValueMaxY;
+ else
+ fPositiveSum += fValueMaxY;
+ }
+ if( fValueMinY < 0 )
+ {
+ if(std::isnan( fNegativeSum ))
+ fNegativeSum = fFirstNegativeY = fValueMinY;
+ else
+ fNegativeSum += fValueMinY;
+ }
+ }
+ rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
+ rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
+ }
+ else
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( std::isnan( fTotalSum ) )
+ {
+ rfMinimumY = fValueMinY;
+ rfMaximumY = fTotalSum = fValueMaxY;
+ }
+ else
+ {
+ fTotalSum += fValueMaxY;
+ if( rfMinimumY > fTotalSum )
+ rfMinimumY = fTotalSum;
+ if( rfMaximumY < fTotalSum )
+ rfMaximumY = fTotalSum;
+ }
+ }
+ }
+
+ aCachedYValues.m_fMinimumY = rfMinimumY;
+ aCachedYValues.m_fMaximumY = rfMaximumY;
+ aCachedYValues.m_bValuesDirty = false;
+ m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
+ sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
+{
+ //@todo maybe cache these values
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ //iterate through the given categories
+ if(nStartCategoryIndex<0)
+ nStartCategoryIndex=0;
+ const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
+ if(nPointCount <= 0)
+ return;
+ if (nEndCategoryIndex >= nPointCount)
+ nEndCategoryIndex = nPointCount - 1;
+ if(nEndCategoryIndex<0)
+ nEndCategoryIndex=0;
+ for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
+ {
+ double fMinimumY = std::numeric_limits<double>::quiet_NaN();
+ double fMaximumY = std::numeric_limits<double>::quiet_NaN();
+
+ calculateYMinAndMaxForCategory( nCatIndex
+ , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
+
+ if(rfMinimumY > fMinimumY)
+ rfMinimumY = fMinimumY;
+ if(rfMaximumY < fMaximumY)
+ rfMaximumY = fMaximumY;
+ }
+}
+
+double VSeriesPlotter::getTransformedDepth() const
+{
+ double MinZ = m_pMainPosHelper->getLogicMinZ();
+ double MaxZ = m_pMainPosHelper->getLogicMaxZ();
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
+ return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
+}
+
+void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
+{
+ if( nAxisIndex<1 )
+ return;
+
+ m_aSecondaryValueScales[nAxisIndex]=rScale;
+}
+
+PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
+{
+ PlottingPositionHelper* pRet = nullptr;
+ if(nAxisIndex>0)
+ {
+ tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
+ if( aPosIt != m_aSecondaryPosHelperMap.end() )
+ {
+ pRet = aPosIt->second.get();
+ }
+ else if (m_pPosHelper)
+ {
+ tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
+ if( aScaleIt != m_aSecondaryValueScales.end() )
+ {
+ m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
+ pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
+ }
+ }
+ }
+ if( !pRet )
+ pRet = m_pMainPosHelper;
+ pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
+ return *pRet;
+}
+
+void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
+{
+}
+
+VDataSeries* VSeriesPlotter::getFirstSeries() const
+{
+ for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ if (!rGroup[0].m_aSeriesVector.empty())
+ {
+ VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
+ if (pSeries)
+ return pSeries;
+ }
+ }
+ }
+ return nullptr;
+}
+
+OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
+{
+ if (m_pExplicitCategoriesProvider)
+ {
+ Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
+ if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
+ {
+ return aCategories[nPointIndex];
+ }
+ }
+ return OUString();
+}
+
+std::vector<VDataSeries const*> VSeriesPlotter::getAllSeries() const
+{
+ std::vector<VDataSeries const*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
+{
+ std::vector<VDataSeries*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+uno::Sequence<OUString> VSeriesPlotter::getSeriesNames() const
+{
+ std::vector<OUString> aRetVector;
+
+ OUString aRole;
+ if (m_xChartTypeModel.is())
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+
+ for (auto const& rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
+ if (!rSeriesGroup.m_aSeriesVector.empty())
+ {
+ VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
+ rtl::Reference< DataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
+ if( xSeries.is() )
+ {
+ OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
+ aRetVector.push_back( aSeriesName );
+ }
+ }
+ }
+ }
+ return comphelper::containerToSequence( aRetVector );
+}
+
+void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
+{
+ m_aPageReferenceSize = rPageRefSize;
+
+ // set reference size also at all data series
+
+ for (auto const & outer : m_aZSlots)
+ for (VDataSeriesGroup const & rGroup : outer)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->setPageReferenceSize(m_aPageReferenceSize);
+ }
+ }
+}
+
+//better performance for big data
+void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
+{
+ m_aCoordinateSystemResolution = rCoordinateSystemResolution;
+}
+
+bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
+{
+ return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
+}
+
+bool VSeriesPlotter::shouldSnapRectToUsedArea()
+{
+ return m_nDimension != 3;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
+ const awt::Size& rEntryKeyAspectRatio
+ , LegendPosition eLegendPosition
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ , ChartModel& rModel
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( xTarget.is() )
+ {
+ rtl::Reference< Diagram > xDiagram = rModel.getFirstChartDiagram();
+ rtl::Reference< BaseCoordinateSystem > xCooSys(xDiagram->getBaseCoordinateSystems()[0]);
+ bool bSwapXAndY = false;
+
+ try
+ {
+ xCooSys->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
+ }
+ catch( const uno::Exception& )
+ {
+ }
+
+ //iterate through all series
+ bool bBreak = false;
+ bool bFirstSeries = true;
+
+
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
+ {
+ continue;
+ }
+
+ std::vector<ViewLegendEntry> aSeriesEntries(
+ createLegendEntriesForSeries(
+ rEntryKeyAspectRatio, *pSeries, xTextProperties,
+ xTarget, xContext));
+
+ //add series entries to the result now
+
+ // use only the first series if VaryColorsByPoint is set for the first series
+ if (bFirstSeries && pSeries->isVaryColorsByPoint())
+ bBreak = true;
+ bFirstSeries = false;
+
+ // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
+ // If the legend is top or bottom and we have a stacked bar-chart the normal order
+ // is the correct one, unless the chart type is horizontal bar-chart.
+ bool bReverse = false;
+ if ( bSwapXAndY )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
+ }
+ else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
+ }
+
+ if (bReverse)
+ aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ else
+ aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ }
+ if (bBreak)
+ return aResult;
+ }
+ }
+ }
+
+ return aResult;
+}
+
+namespace
+{
+bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
+{
+ bool bHasVisibleLine = false;
+ rbHasDashedLine = false;
+ drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
+ if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
+ {
+ if( aLineStyle != drawing::LineStyle_NONE )
+ bHasVisibleLine = true;
+ if( aLineStyle == drawing::LineStyle_DASH )
+ rbHasDashedLine = true;
+ }
+ return bHasVisibleLine;
+}
+
+bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
+{
+ bool bHasRegressionCurves = false;
+ rtl::Reference< DataSeries > xRegrCont( rSeries.getModel() );
+ for( const rtl::Reference< RegressionCurveModel > & rCurve : xRegrCont->getRegressionCurves2() )
+ {
+ bHasRegressionCurves = true;
+ lcl_HasVisibleLine( rCurve, rbHasDashedLine );
+ }
+ return bHasRegressionCurves;
+}
+}
+LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Box;
+}
+
+awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
+{
+ awt::Size aRet(1000,1000);
+ if( m_nDimension==3 )
+ return aRet;
+
+ bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
+ bool bHasLines = false;
+ bool bHasDashedLines = false;
+ //iterate through all series
+ for (VDataSeries* pSeries : getAllSeries())
+ {
+ if( bSeriesAllowsLines )
+ {
+ bool bCurrentDashed = false;
+ if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
+ {
+ bHasLines = true;
+ if( bCurrentDashed )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ bool bRegressionHasDashedLines=false;
+ if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
+ {
+ bHasLines = true;
+ if( bRegressionHasDashedLines )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ if( bHasLines )
+ {
+ if( bHasDashedLines )
+ aRet = awt::Size(1600,-1);
+ else
+ aRet = awt::Size(800,-1);
+ }
+ return aRet;
+}
+
+uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
+{
+ return uno::Any();
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::createLegendSymbolForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle,
+ rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+rtl::Reference< SvxShapeGroup > VSeriesPlotter::createLegendSymbolForPoint(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+
+ // the default properties for the data point are the data series properties.
+ // If a data point has own attributes overwrite them
+ Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
+ Reference< beans::XPropertySet > xPointSet( xSeriesProps );
+ if( rSeries.isAttributedDataPoint( nPointIndex ) )
+ xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
+
+ // if a data point has no own color use a color from the diagram's color scheme
+ if( ! rSeries.hasPointOwnColor( nPointIndex ))
+ {
+ Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
+ if( xCloneable.is() && m_xColorScheme.is() )
+ {
+ xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
+ Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
+ if( xChild.is())
+ xChild->setParent( xSeriesProps );
+
+ OSL_ASSERT( xPointSet.is());
+ xPointSet->setPropertyValue(
+ "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
+ }
+ }
+
+ rtl::Reference< SvxShapeGroup > xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle, xPointSet, ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( ! ( xTarget.is() && xContext.is() ) )
+ return aResult;
+
+ try
+ {
+ ViewLegendEntry aEntry;
+ OUString aLabelText;
+ bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
+ bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
+ CHART2_SERVICE_NAME_CHARTTYPE_PIE);
+ try
+ {
+ if (bIsPie)
+ {
+ bool bDonut = false;
+ if ((m_xChartTypeModel->getPropertyValue("UseRings") >>= bDonut) && bDonut)
+ bIsPie = false;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ if (bVaryColorsByPoint || bIsPie)
+ {
+ Sequence< OUString > aCategoryNames;
+ if( m_pExplicitCategoriesProvider )
+ aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
+ Sequence<sal_Int32> deletedLegendEntries;
+ try
+ {
+ rSeries.getPropertiesOfSeries()->getPropertyValue("DeletedLegendEntries") >>= deletedLegendEntries;
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
+ {
+ bool deletedLegendEntry = false;
+ for (const auto& deletedLegendEntryIdx : std::as_const(deletedLegendEntries))
+ {
+ if (nIdx == deletedLegendEntryIdx)
+ {
+ deletedLegendEntry = true;
+ break;
+ }
+ }
+ if (deletedLegendEntry)
+ continue;
+
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference< SvxShapeGroup > xShape = createLegendSymbolForPoint( rEntryKeyAspectRatio,
+ rSeries, nIdx, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is() )
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = aCategoryNames[nIdx];
+ if( xShape.is() || !aLabelText.isEmpty() )
+ {
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ else
+ {
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = createLegendSymbolForSeries(
+ rEntryKeyAspectRatio, rSeries, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+
+ aResult.push_back(aEntry);
+ }
+
+ // don't show legend entry of regression curve & friends if this type of chart
+ // doesn't support statistics #i63016#, fdo#37197
+ if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
+ return aResult;
+
+ rtl::Reference< DataSeries > xRegrCont = rSeries.getModel();
+ if( xRegrCont.is())
+ {
+ const std::vector< rtl::Reference< RegressionCurveModel > > & aCurves = xRegrCont->getRegressionCurves2();
+ sal_Int32 i = 0, nCount = aCurves.size();
+ for( i=0; i<nCount; ++i )
+ {
+ //label
+ OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
+ replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
+
+ // symbol
+ rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xSymbolGroup, LegendSymbolStyle::Line,
+ aCurves[i],
+ VLegendSymbolFactory::PropertyType::Line, uno::Any() );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
+ ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+ return aResult;
+}
+
+VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
+ const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning )
+{
+ if (!xChartTypeModel.is())
+ return nullptr;
+
+ OUString aChartType = xChartTypeModel->getChartType();
+
+ VSeriesPlotter* pRet=nullptr;
+ if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
+ pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
+ pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
+ pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
+ else
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ return pRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */