/* -*- 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 <DataSeriesProperties.hxx>
#include <CommonConverters.hxx>
#include <ExplicitCategoriesProvider.hxx>
#include <FormattedString.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 "HistogramChart.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 <utility>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <comphelper/diagnose_ex.hxx>
#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 namespace ::chart::DataSeriesProperties;
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( rtl::Reference<ChartType> xChartTypeModel
, sal_Int32 nDimensionCount, bool bCategoryXAxis )
: PlotterBase( nDimensionCount )
, m_pMainPosHelper( nullptr )
, m_xChartTypeModel(std::move(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( u"CharHeight"_ustr) >>= 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(u" "_ustr);
double fRotationDegrees = 0.0;
try
{
uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
if(xPointProps.is())
{
xPointProps->getPropertyValue( u"TextWordWrap"_ustr ) >>= bTextWrap;
xPointProps->getPropertyValue( u"LabelSeparator"_ustr ) >>= aSeparator;
// Extract the optional text rotation through the
// "TextRotation" property attached to the passed data point.
xPointProps->getPropertyValue( u"TextRotation"_ustr ) >>= 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] = xSeries->getLabelForRole( 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] = xSeries->getLabelForRole( 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 : 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( u"TextMaximumFrameWidth"_ustr, 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( u"Transformation"_ustr, 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) &&
// "ShowCustomLeaderLines"
rDataSeries.getModel()->getFastPropertyValue( PROP_DATASERIES_SHOW_CUSTOM_LEADERLINES ).get<sal_Bool>())
{
const basegfx::B2IRectangle aRect(
BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
sal_Int32 nX1 = rScreenPosition2D.X;
sal_Int32 nY1 = rScreenPosition2D.Y;
const sal_Int32 nX2 = std::clamp(nX1, aRelPos.X, aRect.getMaxX());
const sal_Int32 nY2 = std::clamp(nY1, aRect.getMinY(), 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.
if( xSymbol.is() )
{
awt::Point aNewTextPos(xTextShape->getPosition());
awt::Point aSymbolPosition(aNewTextPos);
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
? u"PositiveError"_ustr
: u"NegativeError"_ustr ) >>= 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
? u"PositiveError"_ustr
: u"NegativeError"_ustr ) >>= 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
? u"PositiveError"_ustr
: u"NegativeError"_ustr ) >>= 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( u"ShowPositiveError"_ustr) >>= bShowPositive;
xErrorBarProperties->getPropertyValue( u"ShowNegativeError"_ustr) >>= bShowNegative;
xErrorBarProperties->getPropertyValue( u"ErrorBarStyle"_ustr) >>= 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( u"ErrorBarStyle"_ustr ) >>= nErrorBorderStyleX;
xErrorBorderPropX->getPropertyValue( u"ShowPositiveError"_ustr) >>= bShowXPositive;
xErrorBorderPropX->getPropertyValue( u"ShowNegativeError"_ustr) >>= bShowXNegative;
}
if ( bUseYErrorData )
{
xErrorBorderPropY->getPropertyValue( u"ErrorBarStyle"_ustr ) >>= nErrorBorderStyleY;
xErrorBorderPropY->getPropertyValue( u"ShowPositiveError"_ustr) >>= bShowYPositive;
xErrorBorderPropY->getPropertyValue( u"ShowNegativeError"_ustr) >>= 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 );
const uno::Sequence< double >& aDataX( rVDataSeries.getAllX() );
const 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;
const 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( u"PolynomialDegree"_ustr) >>= aDegree;
rCurve->getPropertyValue( u"MovingAveragePeriod"_ustr) >>= aPeriod;
rCurve->getPropertyValue( u"MovingAverageType"_ustr) >>= aMovingAverageType;
rCurve->getPropertyValue( u"ExtrapolateForward"_ustr) >>= aExtrapolateForward;
rCurve->getPropertyValue( u"ExtrapolateBackward"_ustr) >>= aExtrapolateBackward;
rCurve->getPropertyValue( u"ForceIntercept"_ustr) >>= bForceIntercept;
if (bForceIntercept)
rCurve->getPropertyValue( u"InterceptValue"_ustr) >>= 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 = std::move(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, u"MarkHandles"_ustr );
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( u"ShowEquation"_ustr) >>= bShowEquation ) &&
( xEquationProperties->getPropertyValue( u"ShowCorrelationCoefficient"_ustr) >>= 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( u"XName"_ustr ) >>= aXName) )
aXName = u"x"_ustr;
if ( !(xEquationProperties->getPropertyValue( u"YName"_ustr ) >>= aYName) )
aYName = u"f(x)"_ustr;
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( u"RelativePosition"_ustr) >>= 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();
}
namespace {
// The following it to support rendering order for combo charts. A chart type
// with a lower rendering order is rendered before (i.e., behind) a chart with a
// higher rendering order. The rendering orders are based on rough guesses about
// how much one chart (type) will obscure another chart (type). The intent is to
// minimize obscuring of data, by putting charts that generally cover more
// pixels (e.g., area charts) behind ones that generally cover fewer (e.g., line
// charts).
struct ROrderPair
{
ROrderPair(OUString n, sal_Int32 r) : chartName(n), renderOrder(r) {}
OUString chartName;
sal_Int32 renderOrder;
};
const ROrderPair pairList[] = {
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_AREA, 0),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BAR, 6), // bar & column are same
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_COLUMN, 6),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_HISTOGRAM, 9),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_LINE, 8),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER, 5),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_PIE, 1),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_NET, 3),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET, 2),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK, 7),
ROrderPair(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE, 4)
};
} // unnamed
sal_Int32 VSeriesPlotter::getRenderOrder() const
{
OUString aChartType = m_xChartTypeModel->getChartType();
for (const auto& elem : pairList) {
if (aChartType.equalsIgnoreAsciiCase(elem.chartName)) {
return elem.renderOrder;
}
}
SAL_WARN("chart2", "Unsupported chart type in getRenderOrder()");
return 0;
}
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( xSeries->getLabelForRole( aRole ) );
aRetVector.push_back( aSeriesName );
}
}
}
}
return comphelper::containerToSequence( aRetVector );
}
uno::Sequence<OUString> VSeriesPlotter::getAllSeriesNames() const
{
std::vector<OUString> aRetVector;
OUString aRole;
if (m_xChartTypeModel.is())
aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
for (VDataSeries const* pSeries : getAllSeries())
{
if (pSeries)
{
OUString aSeriesName(pSeries->getModel()->getLabelForRole(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( u"SwapXAndYAxis"_ustr ) >>= 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;
// "ShowLegendEntry"
if (!pSeries->getModel()->getFastPropertyValue(PROP_DATASERIES_SHOW_LEGEND_ENTRY).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;
}
std::vector<ViewLegendSymbol> VSeriesPlotter::createSymbols(const awt::Size& rEntryKeyAspectRatio
, const rtl::Reference<SvxShapeGroupAnyD>& xTarget
, const Reference<uno::XComponentContext>& xContext)
{
std::vector<ViewLegendSymbol> aResult;
if( xTarget.is() )
{
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;
std::vector<ViewLegendSymbol> aSeriesSymbols = createSymbolsForSeries(rEntryKeyAspectRatio, *pSeries, 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;
aResult.insert(aResult.end(), aSeriesSymbols.begin(), aSeriesSymbols.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( u"LineStyle"_ustr) >>= 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;
const 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
const 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(
u"Color"_ustr, 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;
// "UseRings"
if ((m_xChartTypeModel->getFastPropertyValue(PROP_PIECHARTTYPE_USE_RINGS) >>= 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
{
// "DeletedLegendEntries"
rSeries.getModel()->getFastPropertyValue(PROP_DATASERIES_DELETED_LEGEND_ENTRIES) >>= deletedLegendEntries;
}
catch (const uno::Exception&)
{
}
for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
{
bool deletedLegendEntry = false;
for (const auto& deletedLegendEntryIdx : 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 = std::move(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.xLabel = FormattedStringHelper::createFormattedString( 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 = std::move(xSymbolGroup);
OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
ShapeFactory::setShapeName( xShape, aCID );
}
// label
aLabelText = rSeries.getModel()->getLabelForRole( m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : u"values-y"_ustr);
aEntry.xLabel = FormattedStringHelper::createFormattedString( 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;
const 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, u"%SERIESNAME", aLabelText );
aEntry.xLabel = FormattedStringHelper::createFormattedString( 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 = std::move(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;
}
std::vector<ViewLegendSymbol> VSeriesPlotter::createSymbolsForSeries(
const awt::Size& rEntryKeyAspectRatio
, const VDataSeries& rSeries
, const rtl::Reference<SvxShapeGroupAnyD>& xTarget
, const Reference<uno::XComponentContext>& xContext)
{
std::vector<ViewLegendSymbol> aResult;
if (!(xTarget.is() && xContext.is()))
return aResult;
try
{
ViewLegendSymbol aEntry;
// 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 = std::move(xSymbolGroup);
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_HISTOGRAM ) )
pRet = new HistogramChart(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: */
↑ V530 The return value of function 'normalize' is required to be utilized.
↑ V530 The return value of function 'normalize' is required to be utilized.
↑ V547 Expression 'aLineStyle != drawing::LineStyle_NONE' is always false.
↑ V547 Expression 'aLineStyle == drawing::LineStyle_DASH' is always false.
↑ V1058 Nonsensical comparison of two function addresses.
↑ V547 Expression 'bForceIntercept' is always false.
↑ V547 Expression 'bSwapXAndY' is always false.
↑ V560 A part of conditional expression is always true: !bShowNegative.
↑ V560 A part of conditional expression is always true: !bShowPositive.
↑ V560 A part of conditional expression is always true: !bShowXNegative.
↑ V560 A part of conditional expression is always true: !bShowXPositive.
↑ V560 A part of conditional expression is always true: !bShowYNegative.
↑ V560 A part of conditional expression is always true: !bShowYPositive.
↑ V1019 Compound assignment expression is used inside condition.
↑ V1019 Compound assignment expression is used inside condition.