nss: upgrade to release 3.73
[LibreOffice.git] / chart2 / source / view / charttypes / VSeriesPlotter.cxx
blobf405396e113c973627f895f932b58a875d24f314
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <memory>
21 #include <VSeriesPlotter.hxx>
22 #include <BaseGFXHelper.hxx>
23 #include <VLineProperties.hxx>
24 #include <ShapeFactory.hxx>
26 #include <CommonConverters.hxx>
27 #include <ExplicitCategoriesProvider.hxx>
28 #include <ObjectIdentifier.hxx>
29 #include <StatisticsHelper.hxx>
30 #include <PlottingPositionHelper.hxx>
31 #include <LabelPositionHelper.hxx>
32 #include <ChartTypeHelper.hxx>
33 #include <Clipping.hxx>
34 #include <servicenames_charttypes.hxx>
35 #include <NumberFormatterWrapper.hxx>
36 #include <DataSeriesHelper.hxx>
37 #include <RegressionCurveHelper.hxx>
38 #include <VLegendSymbolFactory.hxx>
39 #include <FormattedStringHelper.hxx>
40 #include <RelativePositionHelper.hxx>
41 #include <DateHelper.hxx>
42 #include <DiagramHelper.hxx>
43 #include <defines.hxx>
44 #include <ChartModel.hxx>
46 //only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
47 #include "BarChart.hxx"
48 #include "PieChart.hxx"
49 #include "AreaChart.hxx"
50 #include "CandleStickChart.hxx"
51 #include "BubbleChart.hxx"
52 #include "NetChart.hxx"
53 #include <unonames.hxx>
54 #include <SpecialCharacters.hxx>
56 #include <com/sun/star/chart2/DataPointLabel.hpp>
57 #include <com/sun/star/chart/ErrorBarStyle.hpp>
58 #include <com/sun/star/chart/TimeUnit.hpp>
59 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
60 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
61 #include <com/sun/star/container/XChild.hpp>
62 #include <com/sun/star/chart2/RelativePosition.hpp>
63 #include <tools/color.hxx>
64 #include <rtl/ustrbuf.hxx>
65 #include <rtl/math.hxx>
66 #include <basegfx/vector/b2dvector.hxx>
67 #include <com/sun/star/drawing/LineStyle.hpp>
68 #include <com/sun/star/util/XCloneable.hpp>
69 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
71 #include <com/sun/star/drawing/XShapes.hpp>
73 #include <unotools/localedatawrapper.hxx>
74 #include <comphelper/sequence.hxx>
75 #include <vcl/svapp.hxx>
76 #include <vcl/settings.hxx>
77 #include <tools/diagnose_ex.h>
78 #include <sal/log.hxx>
80 #include <functional>
81 #include <map>
82 #include <unordered_map>
85 namespace chart {
87 using namespace ::com::sun::star;
88 using namespace ::com::sun::star::chart;
89 using namespace ::com::sun::star::chart2;
90 using ::com::sun::star::uno::Reference;
91 using ::com::sun::star::uno::Sequence;
93 VDataSeriesGroup::CachedYValues::CachedYValues()
94 : m_bValuesDirty(true)
95 , m_fMinimumY(0.0)
96 , m_fMaximumY(0.0)
100 VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
101 : m_aSeriesVector(1)
102 , m_bMaxPointCountDirty(true)
103 , m_nMaxPointCount(0)
104 , m_aListOfCachedYValues()
106 m_aSeriesVector[0] = std::move(pSeries);
109 VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
110 : m_aSeriesVector( std::move(other.m_aSeriesVector) )
111 , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
112 , m_nMaxPointCount( other.m_nMaxPointCount )
113 , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
117 VDataSeriesGroup::~VDataSeriesGroup()
121 void VDataSeriesGroup::deleteSeries()
123 //delete all data series help objects:
124 m_aSeriesVector.clear();
127 void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
129 m_aSeriesVector.push_back(std::move(pSeries));
130 m_bMaxPointCountDirty=true;
133 sal_Int32 VDataSeriesGroup::getSeriesCount() const
135 return m_aSeriesVector.size();
138 VSeriesPlotter::VSeriesPlotter( const uno::Reference<XChartType>& xChartTypeModel
139 , sal_Int32 nDimensionCount, bool bCategoryXAxis )
140 : PlotterBase( nDimensionCount )
141 , m_pMainPosHelper( nullptr )
142 , m_xChartTypeModel(xChartTypeModel)
143 , m_xChartTypeModelProps( uno::Reference< beans::XPropertySet >::query( xChartTypeModel ))
144 , m_aZSlots()
145 , m_bCategoryXAxis(bCategoryXAxis)
146 , m_nTimeResolution(css::chart::TimeUnit::DAY)
147 , m_aNullDate(30,12,1899)
148 , m_xColorScheme()
149 , m_pExplicitCategoriesProvider(nullptr)
150 , m_bPointsWereSkipped(false)
151 , m_bPieLabelsAllowToMove(false)
152 , m_aAvailableOuterRect(0, 0, 0, 0)
154 SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
157 VSeriesPlotter::~VSeriesPlotter()
159 //delete all data series help objects:
160 for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
162 for (VDataSeriesGroup & rGroup : rGroupVector)
164 rGroup.deleteSeries();
166 rGroupVector.clear();
168 m_aZSlots.clear();
170 m_aSecondaryPosHelperMap.clear();
172 m_aSecondaryValueScales.clear();
175 void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
177 //take ownership of pSeries
179 OSL_PRECOND( pSeries, "series to add is NULL" );
180 if(!pSeries)
181 return;
183 if(m_bCategoryXAxis)
185 if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
186 pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
187 else
188 pSeries->setCategoryXAxis();
190 else
192 if( m_pExplicitCategoriesProvider )
193 pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
196 if(zSlot<0 || zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
198 //new z slot
199 std::vector< VDataSeriesGroup > aZSlot;
200 aZSlot.emplace_back( std::move(pSeries) );
201 m_aZSlots.push_back( std::move(aZSlot) );
203 else
205 //existing zslot
206 std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
208 if(xSlot<0 || xSlot>=static_cast<sal_Int32>(rXSlots.size()))
210 //append the series to already existing x series
211 rXSlots.emplace_back( std::move(pSeries) );
213 else
215 //x slot is already occupied
216 //y slot decides what to do:
218 VDataSeriesGroup& rYSlots = rXSlots[xSlot];
219 sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
221 if( ySlot < -1 )
223 //move all existing series in the xSlot to next slot
224 //@todo
225 OSL_FAIL( "Not implemented yet");
227 else if( ySlot == -1 || ySlot >= nYSlotCount)
229 //append the series to already existing y series
230 rYSlots.addSeries( std::move(pSeries) );
232 else
234 //y slot is already occupied
235 //insert at given y and x position
237 //@todo
238 OSL_FAIL( "Not implemented yet");
244 drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
246 drawing::Direction3D aRet(1.0,1.0,1.0);
247 if (!m_pPosHelper)
248 return aRet;
250 drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
251 aRet.DirectionZ = aScale.DirectionZ*0.2;
252 if(aRet.DirectionZ>1.0)
253 aRet.DirectionZ=1.0;
254 if(aRet.DirectionZ>10)
255 aRet.DirectionZ=10;
256 return aRet;
259 void VSeriesPlotter::releaseShapes()
261 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
263 for (VDataSeriesGroup const & rGroup : rGroupVector)
265 //iterate through all series in this x slot
266 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
268 pSeries->releaseShapes();
274 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
275 , const uno::Reference< drawing::XShapes >& xTarget )
277 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xGroupShape );
278 if( !xShapes.is() )
280 //create a group shape for this series and add to logic target:
281 xShapes = createGroupShape( xTarget,pDataSeries->getCID() );
282 pDataSeries->m_xGroupShape = xShapes;
284 return xShapes;
287 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
288 , const uno::Reference< drawing::XShapes >& xTarget )
290 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xFrontSubGroupShape );
291 if(!xShapes.is())
293 //ensure that the series group shape is already created
294 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
295 //ensure that the back child is created first
296 getSeriesGroupShapeBackChild( pDataSeries, xTarget );
297 //use series group shape as parent for the new created front group shape
298 xShapes = createGroupShape( xSeriesShapes );
299 pDataSeries->m_xFrontSubGroupShape = xShapes;
301 return xShapes;
304 uno::Reference< drawing::XShapes > VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
305 , const uno::Reference< drawing::XShapes >& xTarget )
307 uno::Reference< drawing::XShapes > xShapes( pDataSeries->m_xBackSubGroupShape );
308 if(!xShapes.is())
310 //ensure that the series group shape is already created
311 uno::Reference< drawing::XShapes > xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
312 //use series group shape as parent for the new created back group shape
313 xShapes = createGroupShape( xSeriesShapes );
314 pDataSeries->m_xBackSubGroupShape = xShapes;
316 return xShapes;
319 uno::Reference< drawing::XShapes > VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
320 , const uno::Reference< drawing::XShapes >& xTextTarget )
322 //xTextTarget needs to be a 2D shape container always!
324 uno::Reference< drawing::XShapes > xShapes( rDataSeries.m_xLabelsGroupShape );
325 if(!xShapes.is())
327 //create a 2D group shape for texts of this series and add to text target:
328 xShapes = m_pShapeFactory->createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
329 rDataSeries.m_xLabelsGroupShape = xShapes;
331 return xShapes;
334 uno::Reference< drawing::XShapes > VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
335 , const uno::Reference< drawing::XShapes >& xTarget
336 , bool bYError )
338 uno::Reference< css::drawing::XShapes > &rShapeGroup =
339 bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
341 uno::Reference< drawing::XShapes > xShapes( rShapeGroup );
342 if(!xShapes.is())
344 //create a group shape for this series and add to logic target:
345 xShapes = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
346 rShapeGroup = xShapes;
348 return xShapes;
352 OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
353 , sal_Int32 nPointIndex
354 , double fValue
355 , bool bAsPercentage )
357 OUString aNumber;
359 if (m_apNumberFormatterWrapper)
361 sal_Int32 nNumberFormatKey = 0;
362 if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
363 nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
364 else if( bAsPercentage )
366 sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
367 if( nPercentFormat != -1 )
368 nNumberFormatKey = nPercentFormat;
370 else
372 nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
374 if(nNumberFormatKey<0)
375 nNumberFormatKey=0;
377 Color nLabelCol;
378 bool bColChanged;
379 aNumber = m_apNumberFormatterWrapper->getFormattedString(
380 nNumberFormatKey, fValue, nLabelCol, bColChanged );
381 //@todo: change color of label if bColChanged is true
383 else
385 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
386 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
387 assert(aNumDecimalSep.getLength() > 0);
388 sal_Unicode cDecSeparator = aNumDecimalSep[0];
389 aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
390 , 3/*DecPlaces*/ , cDecSeparator );
392 return aNumber;
395 uno::Reference< drawing::XShape > VSeriesPlotter::createDataLabel( const uno::Reference< drawing::XShapes >& xTarget
396 , VDataSeries& rDataSeries
397 , sal_Int32 nPointIndex
398 , double fValue
399 , double fSumValue
400 , const awt::Point& rScreenPosition2D
401 , LabelAlignment eAlignment
402 , sal_Int32 nOffset
403 , sal_Int32 nTextWidth )
405 uno::Reference< drawing::XShape > xTextShape;
406 Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
410 const uno::Reference< css::beans::XPropertySet >& xPropertySet(
411 rDataSeries.getPropertiesOfPoint( nPointIndex ) );
412 if( xPropertySet.is() )
414 uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
415 if( aAny.hasValue() )
417 aAny >>= aCustomLabels;
421 awt::Point aScreenPosition2D(rScreenPosition2D);
422 if(eAlignment==LABEL_ALIGN_LEFT)
423 aScreenPosition2D.X -= nOffset;
424 else if(eAlignment==LABEL_ALIGN_RIGHT)
425 aScreenPosition2D.X += nOffset;
426 else if(eAlignment==LABEL_ALIGN_TOP)
427 aScreenPosition2D.Y -= nOffset;
428 else if(eAlignment==LABEL_ALIGN_BOTTOM)
429 aScreenPosition2D.Y += nOffset;
431 uno::Reference< drawing::XShapes > xTarget_ =
432 m_pShapeFactory->createGroup2D(
433 getLabelsGroupShape(rDataSeries, xTarget),
434 ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
436 //check whether the label needs to be created and how:
437 DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
439 if( !pLabel )
440 return xTextShape;
442 //prepare legend symbol
444 // get the font size for the label through the "CharHeight" property
445 // attached to the passed data series entry.
446 // (By tracing font height values it results that for pie chart the
447 // font size is not the same for all labels, but here no font size
448 // modification occurs).
449 float fViewFontSize( 10.0 );
451 uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
452 if( xProps.is() )
453 xProps->getPropertyValue( "CharHeight") >>= fViewFontSize;
454 // pt -> 1/100th mm
455 fViewFontSize *= (2540.0f / 72.0f);
458 // the font height is used for computing the size of an optional legend
459 // symbol to be prepended to the text label.
460 Reference< drawing::XShape > xSymbol;
461 if(pLabel->ShowLegendSymbol)
463 sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
464 awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
465 sal_Int32 nSymbolWidth = aCurrentRatio.Width;
466 if( aCurrentRatio.Height > 0 )
468 nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
470 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
472 if( rDataSeries.isVaryColorsByPoint() )
473 xSymbol.set( VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_, m_xShapeFactory ) );
474 else
475 xSymbol.set( VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_, m_xShapeFactory ) );
478 //prepare text
479 bool bTextWrap = false;
480 OUString aSeparator(" ");
481 double fRotationDegrees = 0.0;
484 uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
485 if(xPointProps.is())
487 xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
488 xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
489 // Extract the optional text rotation through the
490 // "TextRotation" property attached to the passed data point.
491 xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
494 catch( const uno::Exception& )
496 TOOLS_WARN_EXCEPTION("chart2", "" );
499 sal_Int32 nLineCountForSymbolsize = 0;
500 sal_uInt32 nTextListLength = 3;
501 sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
502 Sequence< OUString > aTextList( nTextListLength );
504 bool bUseCustomLabel = nCustomLabelsCount > 0;
505 if( bUseCustomLabel )
507 nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
508 aSeparator = "";
509 aTextList = Sequence< OUString >( nTextListLength );
510 for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
512 switch( aCustomLabels[i]->getFieldType() )
514 case DataPointCustomLabelFieldType_VALUE:
516 aTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
517 break;
519 case DataPointCustomLabelFieldType_CATEGORYNAME:
521 aTextList[i] = getCategoryName( nPointIndex );
522 break;
524 case DataPointCustomLabelFieldType_SERIESNAME:
526 OUString aRole;
527 if ( m_xChartTypeModel )
528 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
529 const uno::Reference< XDataSeries >& xSeries( rDataSeries.getModel() );
530 aTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
531 break;
533 case DataPointCustomLabelFieldType_PERCENTAGE:
535 if(fSumValue == 0.0)
536 fSumValue = 1.0;
537 fValue /= fSumValue;
538 if(fValue < 0)
539 fValue *= -1.0;
541 aTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
542 break;
544 case DataPointCustomLabelFieldType_CELLREF:
545 case DataPointCustomLabelFieldType_CELLRANGE:
547 // TODO: for now doesn't show placeholder
548 aTextList[i] = OUString();
549 break;
551 case DataPointCustomLabelFieldType_TEXT:
553 aTextList[i] = aCustomLabels[i]->getString();
554 break;
556 case DataPointCustomLabelFieldType_NEWLINE:
558 aTextList[i] = "\n";
559 break;
561 default:
562 break;
564 aCustomLabels[i]->setString( aTextList[i] );
567 else
569 if( pLabel->ShowCategoryName )
571 aTextList[0] = getCategoryName( nPointIndex );
574 if( pLabel->ShowNumber )
576 aTextList[1] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
579 if( pLabel->ShowNumberInPercent )
581 if(fSumValue==0.0)
582 fSumValue=1.0;
583 fValue /= fSumValue;
584 if( fValue < 0 )
585 fValue*=-1.0;
587 aTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
591 for( auto const & line : std::as_const(aTextList) )
593 if( !line.isEmpty() )
595 ++nLineCountForSymbolsize;
599 //prepare properties for multipropertyset-interface of shape
600 tNameSequence* pPropNames;
601 tAnySequence* pPropValues;
602 if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
603 return xTextShape;
605 // set text alignment for the text shape to be created.
606 LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
608 // check if data series entry percent value and absolute value have to
609 // be appended to the text label, and what should be the separator
610 // character (comma, space, new line). In case it is a new line we get
611 // a multi-line label.
612 bool bMultiLineLabel = ( aSeparator == "\n" );
614 if( bUseCustomLabel )
616 Sequence< uno::Reference< XFormattedString > > aFormattedLabels( aCustomLabels.getLength() );
617 for( int i = 0; i < aFormattedLabels.getLength(); i++ )
619 aFormattedLabels[i] = aCustomLabels[i];
622 // create text shape
623 xTextShape = ShapeFactory::getOrCreateShapeFactory( m_xShapeFactory )->
624 createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
625 ShapeFactory::makeTransformation( aScreenPosition2D ) );
627 else
629 // join text list elements
630 OUStringBuffer aText;
631 for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
633 if( !aTextList[nN].isEmpty() )
635 if( !aText.isEmpty() )
637 aText.append(aSeparator);
639 aText.append( aTextList[nN] );
643 //create text shape
644 xTextShape = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory)->
645 createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
646 ShapeFactory::makeTransformation( aScreenPosition2D ) );
649 if( !xTextShape.is() )
650 return xTextShape;
652 // we need to use a default value for the maximum width property ?
653 if( nTextWidth == 0 && bTextWrap )
655 sal_Int32 nMinSize =
656 (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
657 ? m_aPageReferenceSize.Height
658 : m_aPageReferenceSize.Width;
659 nTextWidth = nMinSize / 3;
662 // in case text must be wrapped set the maximum width property
663 // for the text shape
664 if( nTextWidth != 0 && bTextWrap )
666 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
667 if( xProp.is() )
669 // compute the height of a line of text
670 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
672 nLineCountForSymbolsize = 1;
674 awt::Size aTextSize = xTextShape->getSize();
675 sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
677 // set maximum text width
678 uno::Any aTextMaximumFrameWidth( nTextWidth );
679 xProp->setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
681 // compute the total lines of text
682 aTextSize = xTextShape->getSize();
683 nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
687 // in case text is rotated, the transformation property of the text
688 // shape is modified.
689 if( fRotationDegrees != 0.0 )
691 const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
692 uno::Reference< beans::XPropertySet > xProp( xTextShape, uno::UNO_QUERY );
693 if( xProp.is() )
694 xProp->setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
695 LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
698 awt::Point aTextShapePos(xTextShape->getPosition());
699 if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
701 awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
702 if( aRelPos.X != -1 )
704 xTextShape->setPosition(aRelPos);
705 if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
706 rDataSeries.getPropertiesOfSeries()->getPropertyValue( "ShowCustomLeaderLines" ).get<sal_Bool>())
708 sal_Int32 nX1 = rScreenPosition2D.X;
709 sal_Int32 nY1 = rScreenPosition2D.Y;
710 sal_Int32 nX2 = nX1;
711 sal_Int32 nY2 = nY1;
712 ::basegfx::B2IRectangle aRect(BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
713 if (nX1 < aRelPos.X)
714 nX2 = aRelPos.X;
715 else if (nX1 > aRect.getMaxX())
716 nX2 = aRect.getMaxX();
718 if (nY1 < aRect.getMinY())
719 nY2 = aRect.getMinY();
720 else if (nY1 > aRect.getMaxY())
721 nY2 = aRect.getMaxY();
723 //when the line is very short compared to the page size don't create one
724 ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
725 double fPageDiagonaleLength = sqrt(double(m_aPageReferenceSize.Width)*double(m_aPageReferenceSize.Width) + double(m_aPageReferenceSize.Height)*double(m_aPageReferenceSize.Height));
726 if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
728 drawing::PointSequenceSequence aPoints(1);
729 aPoints[0].realloc(2);
730 aPoints[0][0].X = nX1;
731 aPoints[0][0].Y = nY1;
732 aPoints[0][1].X = nX2;
733 aPoints[0][1].Y = nY2;
735 VLineProperties aVLineProperties;
736 m_pShapeFactory->createLine2D(xTarget, aPoints, &aVLineProperties);
742 // in case legend symbol has to be displayed, text shape position is
743 // slightly changed.
744 const awt::Point aUnrotatedTextPos(xTextShape->getPosition());
745 if( xSymbol.is() )
747 const awt::Point aOldTextPos( xTextShape->getPosition() );
748 awt::Point aNewTextPos( aOldTextPos );
750 awt::Point aSymbolPosition( aUnrotatedTextPos );
751 awt::Size aSymbolSize( xSymbol->getSize() );
752 awt::Size aTextSize = xTextShape->getSize();
754 sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
755 if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
756 nLineCountForSymbolsize = 1;
757 aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
759 if(eAlignment==LABEL_ALIGN_LEFT
760 || eAlignment==LABEL_ALIGN_LEFT_TOP
761 || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
763 aSymbolPosition.X -= nXDiff;
765 else if(eAlignment==LABEL_ALIGN_RIGHT
766 || eAlignment==LABEL_ALIGN_RIGHT_TOP
767 || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
769 aNewTextPos.X += nXDiff;
771 else if(eAlignment==LABEL_ALIGN_TOP
772 || eAlignment==LABEL_ALIGN_BOTTOM
773 || eAlignment==LABEL_ALIGN_CENTER )
775 aSymbolPosition.X -= nXDiff/2;
776 aNewTextPos.X += nXDiff/2;
779 xSymbol->setPosition( aSymbolPosition );
780 xTextShape->setPosition( aNewTextPos );
783 catch( const uno::Exception& )
785 TOOLS_WARN_EXCEPTION("chart2", "" );
788 return xTextShape;
791 namespace
793 double lcl_getErrorBarLogicLength(
794 const uno::Sequence< double > & rData,
795 const uno::Reference< beans::XPropertySet >& xProp,
796 sal_Int32 nErrorBarStyle,
797 sal_Int32 nIndex,
798 bool bPositive,
799 bool bYError )
801 double fResult;
802 ::rtl::math::setNan( & fResult );
805 switch( nErrorBarStyle )
807 case css::chart::ErrorBarStyle::NONE:
808 break;
809 case css::chart::ErrorBarStyle::VARIANCE:
810 fResult = StatisticsHelper::getVariance( rData );
811 break;
812 case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
813 fResult = StatisticsHelper::getStandardDeviation( rData );
814 break;
815 case css::chart::ErrorBarStyle::RELATIVE:
817 double fPercent = 0;
818 if( xProp->getPropertyValue( bPositive
819 ? OUString("PositiveError")
820 : OUString("NegativeError") ) >>= fPercent )
822 if( nIndex >=0 && nIndex < rData.getLength() &&
823 ! std::isnan( rData[nIndex] ) &&
824 ! std::isnan( fPercent ))
826 fResult = rData[nIndex] * fPercent / 100.0;
830 break;
831 case css::chart::ErrorBarStyle::ABSOLUTE:
832 xProp->getPropertyValue( bPositive
833 ? OUString("PositiveError")
834 : OUString("NegativeError") ) >>= fResult;
835 break;
836 case css::chart::ErrorBarStyle::ERROR_MARGIN:
838 // todo: check if this is really what's called error-margin
839 double fPercent = 0;
840 if( xProp->getPropertyValue( bPositive
841 ? OUString("PositiveError")
842 : OUString("NegativeError") ) >>= fPercent )
844 double fMaxValue;
845 ::rtl::math::setInf(&fMaxValue, true);
846 for(double d : rData)
848 if(fMaxValue < d)
849 fMaxValue = d;
851 if( std::isfinite( fMaxValue ) &&
852 std::isfinite( fPercent ))
854 fResult = fMaxValue * fPercent / 100.0;
858 break;
859 case css::chart::ErrorBarStyle::STANDARD_ERROR:
860 fResult = StatisticsHelper::getStandardError( rData );
861 break;
862 case css::chart::ErrorBarStyle::FROM_DATA:
864 uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
865 if( xErrorBarData.is())
866 fResult = StatisticsHelper::getErrorFromDataSource(
867 xErrorBarData, nIndex, bPositive, bYError);
869 break;
872 catch( const uno::Exception & )
874 TOOLS_WARN_EXCEPTION("chart2", "" );
877 return fResult;
880 void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
881 , drawing::PolyPolygonShape3D& rPoly, sal_Int32 nSequenceIndex )
883 double fFixedWidth = 200.0;
885 aMainDirection.normalize();
886 ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
887 aOrthoDirection.normalize();
889 ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
890 ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
891 ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
893 AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
894 AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
897 ::basegfx::B2DVector lcl_getErrorBarMainDirection(
898 const drawing::Position3D& rStart
899 , const drawing::Position3D& rBottomEnd
900 , PlottingPositionHelper const * pPosHelper
901 , const drawing::Position3D& rUnscaledLogicPosition
902 , bool bYError )
904 ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
905 , rStart.PositionY - rBottomEnd.PositionY );
906 if( !aMainDirection.getLength() )
908 //get logic clip values:
909 double MinX = pPosHelper->getLogicMinX();
910 double MinY = pPosHelper->getLogicMinY();
911 double MaxX = pPosHelper->getLogicMaxX();
912 double MaxY = pPosHelper->getLogicMaxY();
913 double fZ = pPosHelper->getLogicMinZ();
915 if( bYError )
917 //main direction has constant x value
918 MinX = rUnscaledLogicPosition.PositionX;
919 MaxX = rUnscaledLogicPosition.PositionX;
921 else
923 //main direction has constant y value
924 MinY = rUnscaledLogicPosition.PositionY;
925 MaxY = rUnscaledLogicPosition.PositionY;
928 drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
929 drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
931 aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
932 , aStart.PositionY - aEnd.PositionY );
934 if( !aMainDirection.getLength() )
936 //@todo
938 return aMainDirection;
941 drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
942 , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
944 if(!pPosHelper)
945 return drawing::Position3D(0,0,0);
946 pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
947 pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
948 return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
951 } // anonymous namespace
953 void VSeriesPlotter::createErrorBar(
954 const uno::Reference< drawing::XShapes >& xTarget
955 , const drawing::Position3D& rUnscaledLogicPosition
956 , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
957 , const VDataSeries& rVDataSeries
958 , sal_Int32 nIndex
959 , bool bYError /* = true */
960 , const double* pfScaledLogicX
963 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
964 return;
966 if( ! xErrorBarProperties.is())
967 return;
971 bool bShowPositive = false;
972 bool bShowNegative = false;
973 sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
975 xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
976 xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
977 xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
979 if(!bShowPositive && !bShowNegative)
980 return;
982 if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
983 return;
985 if (!m_pPosHelper)
986 return;
988 drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
989 if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
991 if (bYError)
992 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
993 else
994 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
997 bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
998 bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
999 drawing::Position3D aMiddle(aUnscaledLogicPosition);
1000 const double fX = aUnscaledLogicPosition.PositionX;
1001 const double fY = aUnscaledLogicPosition.PositionY;
1002 const double fZ = aUnscaledLogicPosition.PositionZ;
1003 double fScaledX = fX;
1004 if( pfScaledLogicX )
1005 fScaledX = *pfScaledLogicX;
1006 else
1007 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1009 aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
1011 drawing::Position3D aNegative(aMiddle);
1012 drawing::Position3D aPositive(aMiddle);
1014 uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
1016 if( bShowPositive )
1018 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
1019 if( std::isfinite( fLength ) )
1021 double fLocalX = fX;
1022 double fLocalY = fY;
1023 if( bYError )
1025 fLocalY+=fLength;
1026 aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1028 else
1030 fLocalX+=fLength;
1031 aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1033 bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
1035 else
1036 bShowPositive = false;
1039 if( bShowNegative )
1041 double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
1042 if( std::isfinite( fLength ) )
1044 double fLocalX = fX;
1045 double fLocalY = fY;
1046 if( bYError )
1048 fLocalY-=fLength;
1049 aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
1051 else
1053 fLocalX-=fLength;
1054 aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
1056 bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
1058 else
1059 bShowNegative = false;
1062 if(!bShowPositive && !bShowNegative)
1063 return;
1065 drawing::PolyPolygonShape3D aPoly;
1067 sal_Int32 nSequenceIndex=0;
1068 if( bShowNegative )
1069 AddPointToPoly( aPoly, aNegative, nSequenceIndex );
1070 AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
1071 if( bShowPositive )
1072 AddPointToPoly( aPoly, aPositive, nSequenceIndex );
1074 if( bShowNegative && bCreateNegativeBorder )
1076 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
1077 nSequenceIndex++;
1078 lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
1080 if( bShowPositive && bCreatePositiveBorder )
1082 ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
1083 nSequenceIndex++;
1084 lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
1087 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D( xTarget, PolyToPointSequence( aPoly) );
1088 setMappedProperties( xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
1090 catch( const uno::Exception & )
1092 TOOLS_WARN_EXCEPTION("chart2", "" );
1097 void VSeriesPlotter::addErrorBorder(
1098 const drawing::Position3D& rPos0
1099 ,const drawing::Position3D& rPos1
1100 ,const uno::Reference< drawing::XShapes >& rTarget
1101 ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
1103 drawing::PolyPolygonShape3D aPoly;
1104 sal_Int32 nSequenceIndex = 0;
1105 AddPointToPoly( aPoly, rPos0, nSequenceIndex );
1106 AddPointToPoly( aPoly, rPos1, nSequenceIndex );
1107 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1108 rTarget, PolyToPointSequence( aPoly) );
1109 setMappedProperties( xShape, rErrorBorderProp,
1110 PropertyMapper::getPropertyNameMapForLineProperties() );
1113 void VSeriesPlotter::createErrorRectangle(
1114 const drawing::Position3D& rUnscaledLogicPosition
1115 ,VDataSeries& rVDataSeries
1116 ,sal_Int32 nIndex
1117 ,const uno::Reference< drawing::XShapes >& rTarget
1118 ,bool bUseXErrorData
1119 ,bool bUseYErrorData )
1121 if ( m_nDimension != 2 )
1122 return;
1124 // error border properties
1125 Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
1126 if ( bUseXErrorData )
1128 xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
1129 if ( !xErrorBorderPropX.is() )
1130 return;
1132 uno::Reference< drawing::XShapes > xErrorBorder_ShapesX(
1133 getErrorBarsGroupShape( rVDataSeries, rTarget, false ) );
1135 if ( bUseYErrorData )
1137 xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
1138 if ( !xErrorBorderPropY.is() )
1139 return;
1141 uno::Reference< drawing::XShapes > xErrorBorder_ShapesY(
1142 getErrorBarsGroupShape( rVDataSeries, rTarget, true ) );
1144 if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
1145 return;
1149 bool bShowXPositive = false;
1150 bool bShowXNegative = false;
1151 bool bShowYPositive = false;
1152 bool bShowYNegative = false;
1154 sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
1155 sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
1157 if ( bUseXErrorData )
1159 xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
1160 xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
1161 xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
1163 if ( bUseYErrorData )
1165 xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
1166 xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
1167 xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
1170 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
1171 bUseXErrorData = false;
1172 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
1173 bUseYErrorData = false;
1175 if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
1176 return;
1178 if ( !m_pPosHelper )
1179 return;
1181 drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
1182 if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1183 aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
1184 if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
1185 aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
1187 const double fX = aUnscaledLogicPosition.PositionX;
1188 const double fY = aUnscaledLogicPosition.PositionY;
1189 const double fZ = aUnscaledLogicPosition.PositionZ;
1190 double fScaledX = fX;
1191 m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
1193 uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
1194 uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
1196 double fPosX = 0.0;
1197 double fPosY = 0.0;
1198 double fNegX = 0.0;
1199 double fNegY = 0.0;
1200 if ( bUseXErrorData )
1202 if ( bShowXPositive )
1203 fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1204 nErrorBorderStyleX, nIndex, true, false );
1205 if ( bShowXNegative )
1206 fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
1207 nErrorBorderStyleX, nIndex, false, false );
1210 if ( bUseYErrorData )
1212 if ( bShowYPositive )
1213 fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1214 nErrorBorderStyleY, nIndex, true, true );
1215 if ( bShowYNegative )
1216 fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
1217 nErrorBorderStyleY, nIndex, false, true );
1220 if ( !( std::isfinite( fPosX ) &&
1221 std::isfinite( fPosY ) &&
1222 std::isfinite( fNegX ) &&
1223 std::isfinite( fNegY ) ) )
1224 return;
1226 drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
1227 fX - fNegX, fY - fNegY, fZ ) );
1228 drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
1229 fX - fNegX, fY + fPosY, fZ ) );
1230 drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
1231 fX + fPosX, fY + fPosY, fZ ) );
1232 drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
1233 fX + fPosX, fY - fNegY, fZ ) );
1234 if ( bUseXErrorData )
1236 // top border
1237 addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
1239 // bottom border
1240 addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
1243 if ( bUseYErrorData )
1245 // left border
1246 addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
1248 // right border
1249 addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
1252 catch( const uno::Exception & )
1254 DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
1258 void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
1259 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1260 , const uno::Reference< drawing::XShapes >& xTarget )
1262 if(m_nDimension!=2)
1263 return;
1264 // error bars
1265 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
1266 if( xErrorBarProp.is())
1268 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1269 getErrorBarsGroupShape(rVDataSeries, xTarget, false) );
1271 createErrorBar( xErrorBarsGroup_Shapes
1272 , rUnscaledLogicPosition, xErrorBarProp
1273 , rVDataSeries, nPointIndex
1274 , false /* bYError */
1275 , nullptr );
1279 void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
1280 , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
1281 , const uno::Reference< drawing::XShapes >& xTarget
1282 , double const * pfScaledLogicX )
1284 if(m_nDimension!=2)
1285 return;
1286 // error bars
1287 uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
1288 if( xErrorBarProp.is())
1290 uno::Reference< drawing::XShapes > xErrorBarsGroup_Shapes(
1291 getErrorBarsGroupShape(rVDataSeries, xTarget, true) );
1293 createErrorBar( xErrorBarsGroup_Shapes
1294 , rUnscaledLogicPosition, xErrorBarProp
1295 , rVDataSeries, nPointIndex
1296 , true /* bYError */
1297 , pfScaledLogicX );
1301 void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
1302 const uno::Reference< drawing::XShapes >& xTarget,
1303 const uno::Reference< drawing::XShapes >& xEquationTarget,
1304 bool bMaySkipPoints )
1306 if(m_nDimension!=2)
1307 return;
1308 uno::Reference< XRegressionCurveContainer > xContainer( rVDataSeries.getModel(), uno::UNO_QUERY );
1309 if(!xContainer.is())
1310 return;
1312 if (!m_pPosHelper)
1313 return;
1315 uno::Sequence< uno::Reference< XRegressionCurve > > aCurveList = xContainer->getRegressionCurves();
1317 for(sal_Int32 nN=0; nN<aCurveList.getLength(); nN++)
1319 uno::Reference< XRegressionCurveCalculator > xCalculator( aCurveList[nN]->getCalculator() );
1320 if( !xCalculator.is())
1321 continue;
1323 uno::Reference< beans::XPropertySet > xProperties( aCurveList[nN], uno::UNO_QUERY );
1325 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurveList[nN] );
1327 sal_Int32 aDegree = 2;
1328 sal_Int32 aPeriod = 2;
1329 double aExtrapolateForward = 0.0;
1330 double aExtrapolateBackward = 0.0;
1331 bool bForceIntercept = false;
1332 double aInterceptValue = 0.0;
1334 if ( xProperties.is() && !bAverageLine )
1336 xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
1337 xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
1338 xProperties->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
1339 xProperties->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
1340 xProperties->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
1341 if (bForceIntercept)
1342 xProperties->getPropertyValue( "InterceptValue") >>= aInterceptValue;
1345 double fChartMinX = m_pPosHelper->getLogicMinX();
1346 double fChartMaxX = m_pPosHelper->getLogicMaxX();
1348 double fMinX = fChartMinX;
1349 double fMaxX = fChartMaxX;
1351 double fPointScale = 1.0;
1353 if( !bAverageLine )
1355 rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
1356 fMaxX += aExtrapolateForward;
1357 fMinX -= aExtrapolateBackward;
1359 fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
1360 // sanitize the value, tdf#119922
1361 fPointScale = std::min(fPointScale, 1000.0);
1364 xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod);
1365 xCalculator->recalculateRegression( rVDataSeries.getAllX(), rVDataSeries.getAllY() );
1366 sal_Int32 nPointCount = 100 * fPointScale;
1368 if ( nPointCount < 2 )
1369 nPointCount = 2;
1371 std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
1372 uno::Reference< chart2::XScaling > xScalingX;
1373 uno::Reference< chart2::XScaling > xScalingY;
1374 if( aScales.size() >= 2 )
1376 xScalingX.set( aScales[0].Scaling );
1377 xScalingY.set( aScales[1].Scaling );
1380 const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
1381 xCalculator->getCurveValues(
1382 fMinX, fMaxX, nPointCount,
1383 xScalingX, xScalingY, bMaySkipPoints ));
1385 nPointCount = aCalculatedPoints.getLength();
1387 drawing::PolyPolygonShape3D aRegressionPoly;
1388 aRegressionPoly.SequenceX.realloc(1);
1389 aRegressionPoly.SequenceY.realloc(1);
1390 aRegressionPoly.SequenceZ.realloc(1);
1391 aRegressionPoly.SequenceX[0].realloc(nPointCount);
1392 aRegressionPoly.SequenceY[0].realloc(nPointCount);
1393 aRegressionPoly.SequenceZ[0].realloc(nPointCount);
1395 sal_Int32 nRealPointCount = 0;
1397 for(geometry::RealPoint2D const & p : aCalculatedPoints)
1399 double fLogicX = p.X;
1400 double fLogicY = p.Y;
1401 double fLogicZ = 0.0; //dummy
1403 // fdo#51656: don't scale mean value lines
1404 if(!bAverageLine)
1405 m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
1407 if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
1408 !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
1409 !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
1411 aRegressionPoly.SequenceX[0][nRealPointCount] = fLogicX;
1412 aRegressionPoly.SequenceY[0][nRealPointCount] = fLogicY;
1413 nRealPointCount++;
1416 aRegressionPoly.SequenceX[0].realloc(nRealPointCount);
1417 aRegressionPoly.SequenceY[0].realloc(nRealPointCount);
1418 aRegressionPoly.SequenceZ[0].realloc(nRealPointCount);
1420 drawing::PolyPolygonShape3D aClippedPoly;
1421 Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
1422 aRegressionPoly = aClippedPoly;
1423 m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
1425 awt::Point aDefaultPos;
1426 if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
1428 VLineProperties aVLineProperties;
1429 aVLineProperties.initFromPropertySet( xProperties );
1431 //create an extra group shape for each curve for selection handling
1432 uno::Reference< drawing::XShapes > xRegressionGroupShapes =
1433 createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
1434 uno::Reference< drawing::XShape > xShape = m_pShapeFactory->createLine2D(
1435 xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
1436 ShapeFactory::setShapeName( xShape, "MarkHandles" );
1437 aDefaultPos = xShape->getPosition();
1440 // curve equation and correlation coefficient
1441 uno::Reference< beans::XPropertySet > xEquationProperties( aCurveList[nN]->getEquationProperties());
1442 if( xEquationProperties.is())
1444 createRegressionCurveEquationShapes(
1445 rVDataSeries.getDataCurveEquationCID( nN ),
1446 xEquationProperties, xEquationTarget, xCalculator,
1447 aDefaultPos );
1452 static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
1454 const sal_Int32 nStringLength = aString.getLength();
1455 sal_Int32 nMaxLineLength = 0;
1457 for ( sal_Int32 i=0; i<nStringLength; i++ )
1459 sal_Int32 indexSep = aString.indexOf( "\n", i );
1460 if ( indexSep < 0 )
1461 indexSep = nStringLength;
1462 sal_Int32 nLineLength = indexSep - i;
1463 if ( nLineLength > nMaxLineLength )
1464 nMaxLineLength = nLineLength;
1465 i = indexSep;
1468 return nMaxLineLength;
1471 void VSeriesPlotter::createRegressionCurveEquationShapes(
1472 const OUString & rEquationCID,
1473 const uno::Reference< beans::XPropertySet > & xEquationProperties,
1474 const uno::Reference< drawing::XShapes >& xEquationTarget,
1475 const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
1476 awt::Point aDefaultPos )
1478 OSL_ASSERT( xEquationProperties.is());
1479 if( !xEquationProperties.is())
1480 return;
1482 bool bShowEquation = false;
1483 bool bShowCorrCoeff = false;
1484 if(!(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
1485 ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff )))
1486 return;
1488 if( ! (bShowEquation || bShowCorrCoeff))
1489 return;
1491 OUStringBuffer aFormula;
1492 sal_Int32 nNumberFormatKey = 0;
1493 sal_Int32 nFormulaWidth = 0;
1494 xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
1495 bool bResizeEquation = true;
1496 sal_Int32 nMaxIteration = 2;
1497 if ( bShowEquation )
1499 OUString aXName, aYName;
1500 if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
1501 aXName = OUString( "x" );
1502 if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
1503 aYName = OUString( "f(x)" );
1504 xRegressionCurveCalculator->setXYNames( aXName, aYName );
1507 for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
1509 bResizeEquation = false;
1510 if( bShowEquation )
1512 if (m_apNumberFormatterWrapper)
1513 { // iteration 0: default representation (no wrap)
1514 // iteration 1: expected width (nFormulaWidth) is calculated
1515 aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
1516 m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
1517 nNumberFormatKey, nFormulaWidth );
1518 nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
1520 else
1522 aFormula = xRegressionCurveCalculator->getRepresentation();
1525 if( bShowCorrCoeff )
1527 aFormula.append( "\n" );
1530 if( bShowCorrCoeff )
1532 aFormula.append( "R" ).append( OUStringChar( aSuperscriptFigures[2] ) ).append( " = " );
1533 double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
1534 if (m_apNumberFormatterWrapper)
1536 Color nLabelCol;
1537 bool bColChanged;
1538 aFormula.append(
1539 m_apNumberFormatterWrapper->getFormattedString(
1540 nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
1541 //@todo: change color of label if bColChanged is true
1543 else
1545 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
1546 const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
1547 sal_Unicode aDecimalSep = aNumDecimalSep[0];
1548 aFormula.append( ::rtl::math::doubleToUString(
1549 fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
1553 awt::Point aScreenPosition2D;
1554 chart2::RelativePosition aRelativePosition;
1555 if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
1557 //@todo decide whether x is primary or secondary
1558 double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
1559 double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
1560 aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
1561 aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
1563 else
1564 aScreenPosition2D = aDefaultPos;
1566 if( !aFormula.isEmpty())
1568 // set fill and line properties on creation
1569 tNameSequence aNames;
1570 tAnySequence aValues;
1571 PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
1573 uno::Reference< drawing::XShape > xTextShape = m_pShapeFactory->createText(
1574 xEquationTarget, aFormula.makeStringAndClear(),
1575 aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
1577 OSL_ASSERT( xTextShape.is());
1578 if( xTextShape.is())
1580 ShapeFactory::setShapeName( xTextShape, rEquationCID );
1581 awt::Size aSize( xTextShape->getSize() );
1582 awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
1583 aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
1584 //ensure that the equation is fully placed within the page (if possible)
1585 if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
1586 aPos.X = m_aPageReferenceSize.Width - aSize.Width;
1587 if( aPos.X < 0 )
1589 aPos.X = 0;
1590 if ( nFormulaWidth > 0 )
1592 bResizeEquation = true;
1593 if ( nCountIteration < nMaxIteration-1 )
1594 xEquationTarget->remove( xTextShape ); // remove equation
1595 nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
1596 nFormulaWidth -= nCountIteration;
1597 if ( nFormulaWidth < 0 )
1598 nFormulaWidth = 0;
1601 if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
1602 aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
1603 if( aPos.Y < 0 )
1604 aPos.Y = 0;
1605 if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
1606 xTextShape->setPosition(aPos); // if equation was not removed
1612 void VSeriesPlotter::setMappedProperties(
1613 const uno::Reference< drawing::XShape >& xTargetShape
1614 , const uno::Reference< beans::XPropertySet >& xSource
1615 , const tPropertyNameMap& rMap
1616 , tPropertyNameValueMap const * pOverwriteMap )
1618 uno::Reference< beans::XPropertySet > xTargetProp( xTargetShape, uno::UNO_QUERY );
1619 PropertyMapper::setMappedProperties(xTargetProp,xSource,rMap,pOverwriteMap);
1622 void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
1624 m_nTimeResolution = TimeResolution;
1625 m_aNullDate = rNullDate;
1628 // MinimumAndMaximumSupplier
1629 tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
1631 tools::Long nRet = css::chart::TimeUnit::YEAR;
1632 if (!m_pExplicitCategoriesProvider)
1633 return nRet;
1635 const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
1636 if (rDateCategories.empty())
1637 return nRet;
1639 std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
1641 aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
1642 if (aIt == aEnd)
1643 return nRet;
1645 Date aNullDate(30,12,1899);
1646 if (m_apNumberFormatterWrapper)
1647 aNullDate = m_apNumberFormatterWrapper->getNullDate();
1649 Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
1650 ++aIt;
1651 for(;aIt!=aEnd;++aIt)
1653 if (std::isnan(*aIt))
1654 continue;
1656 Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
1657 if( nRet == css::chart::TimeUnit::YEAR )
1659 if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
1660 nRet = css::chart::TimeUnit::MONTH;
1662 if( nRet == css::chart::TimeUnit::MONTH )
1664 if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
1665 nRet = css::chart::TimeUnit::DAY;
1667 if( nRet == css::chart::TimeUnit::DAY )
1668 break;
1669 aPrevious=aCurrent;
1672 return nRet;
1674 double VSeriesPlotter::getMinimumX()
1676 double fMinimum, fMaximum;
1677 getMinimumAndMaximumX( fMinimum, fMaximum );
1678 return fMinimum;
1680 double VSeriesPlotter::getMaximumX()
1682 double fMinimum, fMaximum;
1683 getMinimumAndMaximumX( fMinimum, fMaximum );
1684 return fMaximum;
1687 double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1689 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1691 double fMinY, fMaxY;
1692 getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1693 return fMinY;
1696 double fMinimum, fMaximum;
1697 ::rtl::math::setInf(&fMinimum, false);
1698 ::rtl::math::setInf(&fMaximum, true);
1699 for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
1701 for(VDataSeriesGroup & rXSlot : rXSlots)
1703 double fLocalMinimum, fLocalMaximum;
1704 rXSlot.calculateYMinAndMaxForCategoryRange(
1705 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1706 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1707 , isSeparateStackingForDifferentSigns( 1 )
1708 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1709 if(fMaximum<fLocalMaximum)
1710 fMaximum=fLocalMaximum;
1711 if(fMinimum>fLocalMinimum)
1712 fMinimum=fLocalMinimum;
1715 if(std::isinf(fMinimum))
1716 ::rtl::math::setNan(&fMinimum);
1717 return fMinimum;
1720 double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
1722 if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
1724 double fMinY, fMaxY;
1725 getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
1726 return fMaxY;
1729 double fMinimum, fMaximum;
1730 ::rtl::math::setInf(&fMinimum, false);
1731 ::rtl::math::setInf(&fMaximum, true);
1732 for( std::vector< VDataSeriesGroup > & rXSlots : m_aZSlots)
1734 for(VDataSeriesGroup & rXSlot : rXSlots)
1736 double fLocalMinimum, fLocalMaximum;
1737 rXSlot.calculateYMinAndMaxForCategoryRange(
1738 static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
1739 , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
1740 , isSeparateStackingForDifferentSigns( 1 )
1741 , fLocalMinimum, fLocalMaximum, nAxisIndex );
1742 if(fMaximum<fLocalMaximum)
1743 fMaximum=fLocalMaximum;
1744 if(fMinimum>fLocalMinimum)
1745 fMinimum=fLocalMinimum;
1748 if(std::isinf(fMaximum))
1749 ::rtl::math::setNan(&fMaximum);
1750 return fMaximum;
1753 double VSeriesPlotter::getMinimumZ()
1755 //this is the default for all charts without a meaningful z axis
1756 return 1.0;
1758 double VSeriesPlotter::getMaximumZ()
1760 if( m_nDimension!=3 || m_aZSlots.empty() )
1761 return getMinimumZ()+1;
1762 return m_aZSlots.size();
1765 namespace
1767 bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
1769 // default implementation: true for Y axes, and for value X axis
1770 if( nDimensionIndex == 0 )
1771 return !bCategoryXAxis;
1772 return nDimensionIndex == 1;
1776 bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
1778 return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1781 bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
1783 // do not expand axes in 3D charts
1784 return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
1787 bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
1789 // default implementation: only for Y axis
1790 return nDimensionIndex == 1;
1793 bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
1795 // default implementation: only for Y axis
1796 return nDimensionIndex == 1;
1799 bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
1801 // default implementation: only for Y axis
1802 return nDimensionIndex == 1;
1805 void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1807 ::rtl::math::setInf(&rfMinimum, false);
1808 ::rtl::math::setInf(&rfMaximum, true);
1810 for (auto const& ZSlot : m_aZSlots)
1812 for (auto const& XSlot : ZSlot)
1814 double fLocalMinimum, fLocalMaximum;
1815 XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
1816 if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
1817 rfMinimum = fLocalMinimum;
1818 if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
1819 rfMaximum = fLocalMaximum;
1822 if(std::isinf(rfMinimum))
1823 ::rtl::math::setNan(&rfMinimum);
1824 if(std::isinf(rfMaximum))
1825 ::rtl::math::setNan(&rfMaximum);
1828 void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
1830 ::rtl::math::setInf(&rfMinY, false);
1831 ::rtl::math::setInf(&rfMaxY, true);
1833 for (auto const& ZSlot : m_aZSlots)
1835 for (auto const& XSlot : ZSlot)
1837 double fLocalMinimum, fLocalMaximum;
1838 XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
1839 if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
1840 rfMinY = fLocalMinimum;
1841 if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
1842 rfMaxY = fLocalMaximum;
1845 if(std::isinf(rfMinY))
1846 ::rtl::math::setNan(&rfMinY);
1847 if(std::isinf(rfMaxY))
1848 ::rtl::math::setNan(&rfMaxY);
1851 sal_Int32 VSeriesPlotter::getPointCount() const
1853 sal_Int32 nRet = 0;
1855 for (auto const& ZSlot : m_aZSlots)
1857 for (auto const& XSlot : ZSlot)
1859 sal_Int32 nPointCount = XSlot.getPointCount();
1860 if( nPointCount>nRet )
1861 nRet = nPointCount;
1864 return nRet;
1867 void VSeriesPlotter::setNumberFormatsSupplier(
1868 const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
1870 m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
1873 void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
1875 m_xColorScheme = xColorScheme;
1878 void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
1880 m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
1883 sal_Int32 VDataSeriesGroup::getPointCount() const
1885 if(!m_bMaxPointCountDirty)
1886 return m_nMaxPointCount;
1888 sal_Int32 nRet = 0;
1890 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1892 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1893 if( nPointCount>nRet )
1894 nRet = nPointCount;
1896 m_nMaxPointCount=nRet;
1897 m_aListOfCachedYValues.clear();
1898 m_aListOfCachedYValues.resize(m_nMaxPointCount);
1899 m_bMaxPointCountDirty=false;
1900 return nRet;
1903 sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
1905 sal_Int32 nRet = 0;
1907 if (!m_aSeriesVector.empty())
1908 nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
1910 return nRet;
1913 void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
1916 ::rtl::math::setInf(&rfMinimum, false);
1917 ::rtl::math::setInf(&rfMaximum, true);
1919 for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
1921 sal_Int32 nPointCount = pSeries->getTotalPointCount();
1922 for(sal_Int32 nN=0;nN<nPointCount;nN++)
1924 double fX = pSeries->getXValue( nN );
1925 if( std::isnan(fX) )
1926 continue;
1927 if(rfMaximum<fX)
1928 rfMaximum=fX;
1929 if(rfMinimum>fX)
1930 rfMinimum=fX;
1933 if(std::isinf(rfMinimum))
1934 ::rtl::math::setNan(&rfMinimum);
1935 if(std::isinf(rfMaximum))
1936 ::rtl::math::setNan(&rfMaximum);
1939 namespace {
1942 * Keep track of minimum and maximum Y values for one or more data series.
1943 * When multiple data series exist, that indicates that the data series are
1944 * stacked.
1946 * <p>For each X value, we calculate separate Y value ranges for each data
1947 * series in the first pass. In the second pass, we calculate the minimum Y
1948 * value by taking the absolute minimum value of all data series, whereas
1949 * the maximum Y value is the sum of all the series maximum Y values.</p>
1951 * <p>Once that's done for all X values, the final min / max Y values get
1952 * calculated by taking the absolute min / max Y values across all the X
1953 * values.</p>
1955 class PerXMinMaxCalculator
1957 typedef std::pair<double, double> MinMaxType;
1958 typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
1959 typedef std::map<double, std::unique_ptr<SeriesMinMaxType>> GroupMinMaxType;
1960 typedef std::unordered_map<double, MinMaxType> TotalStoreType;
1961 GroupMinMaxType m_SeriesGroup;
1962 size_t mnCurSeries;
1964 public:
1965 PerXMinMaxCalculator() : mnCurSeries(0) {}
1967 void nextSeries() { ++mnCurSeries; }
1969 void setValue(double fX, double fY)
1971 SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
1972 if (!pStore)
1973 // This shouldn't happen!
1974 return;
1976 SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
1977 if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
1979 MinMaxType& r = it->second;
1980 // A min-max pair already exists for this series. Update it.
1981 if (fY < r.first)
1982 r.first = fY;
1983 if (r.second < fY)
1984 r.second = fY;
1986 else
1988 // No existing pair. Insert a new one.
1989 pStore->insert(
1990 it, SeriesMinMaxType::value_type(
1991 mnCurSeries, MinMaxType(fY,fY)));
1995 void getTotalRange(double& rfMin, double& rfMax) const
1997 rtl::math::setNan(&rfMin);
1998 rtl::math::setNan(&rfMax);
2000 TotalStoreType aStore;
2001 getTotalStore(aStore);
2003 if (aStore.empty())
2004 return;
2006 TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
2007 rfMin = it->second.first;
2008 rfMax = it->second.second;
2009 for (++it; it != itEnd; ++it)
2011 if (rfMin > it->second.first)
2012 rfMin = it->second.first;
2013 if (rfMax < it->second.second)
2014 rfMax = it->second.second;
2018 private:
2020 * Parse all data and reduce them into a set of global Y value ranges per
2021 * X value.
2023 void getTotalStore(TotalStoreType& rStore) const
2025 TotalStoreType aStore;
2026 for (auto const& it : m_SeriesGroup)
2028 double fX = it.first;
2030 const SeriesMinMaxType& rSeries = *it.second;
2031 for (auto const& series : rSeries)
2033 double fYMin = series.second.first, fYMax = series.second.second;
2034 TotalStoreType::iterator itr = aStore.find(fX);
2035 if (itr == aStore.end())
2036 // New min-max pair for give X value.
2037 aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
2038 else
2040 MinMaxType& r = itr->second;
2041 if (fYMin < r.first)
2042 r.first = fYMin; // min y-value
2044 r.second += fYMax; // accumulative max y-value.
2048 rStore.swap(aStore);
2051 SeriesMinMaxType* getByXValue(double fX)
2053 GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
2054 if (it == m_SeriesGroup.end())
2056 std::pair<GroupMinMaxType::iterator,bool> r =
2057 m_SeriesGroup.insert(std::make_pair(fX, std::make_unique<SeriesMinMaxType>()));
2059 if (!r.second)
2060 // insertion failed.
2061 return nullptr;
2063 it = r.first;
2066 return it->second.get();
2072 void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
2073 double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
2075 ::rtl::math::setNan(&rfMinY);
2076 ::rtl::math::setNan(&rfMaxY);
2078 if (m_aSeriesVector.empty())
2079 // No data series. Bail out.
2080 return;
2082 PerXMinMaxCalculator aRangeCalc;
2083 for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
2085 if (!pSeries)
2086 continue;
2088 for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
2090 if (nAxisIndex != pSeries->getAttachedAxisIndex())
2091 continue;
2093 double fX = pSeries->getXValue(i);
2094 if (std::isnan(fX))
2095 continue;
2097 if (fX < fMinX || fX > fMaxX)
2098 // Outside specified X range. Skip it.
2099 continue;
2101 double fY = pSeries->getYValue(i);
2102 if (std::isnan(fY))
2103 continue;
2105 aRangeCalc.setValue(fX, fY);
2107 aRangeCalc.nextSeries();
2110 aRangeCalc.getTotalRange(rfMinY, rfMaxY);
2113 void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
2114 , bool bSeparateStackingForDifferentSigns
2115 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2117 assert(nCategoryIndex >= 0);
2118 assert(nCategoryIndex < getPointCount());
2120 ::rtl::math::setInf(&rfMinimumY, false);
2121 ::rtl::math::setInf(&rfMaximumY, true);
2123 if(m_aSeriesVector.empty())
2124 return;
2126 CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
2127 if( !aCachedYValues.m_bValuesDirty )
2129 //return cached values
2130 rfMinimumY = aCachedYValues.m_fMinimumY;
2131 rfMaximumY = aCachedYValues.m_fMaximumY;
2132 return;
2135 double fTotalSum, fPositiveSum, fNegativeSum, fFirstPositiveY, fFirstNegativeY;
2136 ::rtl::math::setNan( &fTotalSum );
2137 ::rtl::math::setNan( &fPositiveSum );
2138 ::rtl::math::setNan( &fNegativeSum );
2139 ::rtl::math::setNan( &fFirstPositiveY );
2140 ::rtl::math::setNan( &fFirstNegativeY );
2142 if( bSeparateStackingForDifferentSigns )
2144 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2146 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2147 continue;
2149 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2150 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2152 if( fValueMaxY >= 0 )
2154 if( std::isnan( fPositiveSum ) )
2155 fPositiveSum = fFirstPositiveY = fValueMaxY;
2156 else
2157 fPositiveSum += fValueMaxY;
2159 if( fValueMinY < 0 )
2161 if(std::isnan( fNegativeSum ))
2162 fNegativeSum = fFirstNegativeY = fValueMinY;
2163 else
2164 fNegativeSum += fValueMinY;
2167 rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
2168 rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
2170 else
2172 for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
2174 if( nAxisIndex != pSeries->getAttachedAxisIndex() )
2175 continue;
2177 double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
2178 double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
2180 if( std::isnan( fTotalSum ) )
2182 rfMinimumY = fValueMinY;
2183 rfMaximumY = fTotalSum = fValueMaxY;
2185 else
2187 fTotalSum += fValueMaxY;
2188 if( rfMinimumY > fTotalSum )
2189 rfMinimumY = fTotalSum;
2190 if( rfMaximumY < fTotalSum )
2191 rfMaximumY = fTotalSum;
2196 aCachedYValues.m_fMinimumY = rfMinimumY;
2197 aCachedYValues.m_fMaximumY = rfMaximumY;
2198 aCachedYValues.m_bValuesDirty = false;
2199 m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
2202 void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
2203 sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
2204 , bool bSeparateStackingForDifferentSigns
2205 , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
2207 //@todo maybe cache these values
2208 ::rtl::math::setInf(&rfMinimumY, false);
2209 ::rtl::math::setInf(&rfMaximumY, true);
2211 //iterate through the given categories
2212 if(nStartCategoryIndex<0)
2213 nStartCategoryIndex=0;
2214 const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
2215 if(nPointCount <= 0)
2216 return;
2217 if (nEndCategoryIndex >= nPointCount)
2218 nEndCategoryIndex = nPointCount - 1;
2219 if(nEndCategoryIndex<0)
2220 nEndCategoryIndex=0;
2221 for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
2223 double fMinimumY; ::rtl::math::setNan(&fMinimumY);
2224 double fMaximumY; ::rtl::math::setNan(&fMaximumY);
2226 calculateYMinAndMaxForCategory( nCatIndex
2227 , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
2229 if(rfMinimumY > fMinimumY)
2230 rfMinimumY = fMinimumY;
2231 if(rfMaximumY < fMaximumY)
2232 rfMaximumY = fMaximumY;
2236 double VSeriesPlotter::getTransformedDepth() const
2238 double MinZ = m_pMainPosHelper->getLogicMinZ();
2239 double MaxZ = m_pMainPosHelper->getLogicMaxZ();
2240 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
2241 m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
2242 return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
2245 void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
2247 if( nAxisIndex<1 )
2248 return;
2250 m_aSecondaryValueScales[nAxisIndex]=rScale;
2253 PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
2255 PlottingPositionHelper* pRet = nullptr;
2256 if(nAxisIndex>0)
2258 tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
2259 if( aPosIt != m_aSecondaryPosHelperMap.end() )
2261 pRet = aPosIt->second.get();
2263 else if (m_pPosHelper)
2265 tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
2266 if( aScaleIt != m_aSecondaryValueScales.end() )
2268 m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
2269 pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
2273 if( !pRet )
2274 pRet = m_pMainPosHelper;
2275 if(pRet)
2276 pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
2277 return *pRet;
2280 void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
2284 VDataSeries* VSeriesPlotter::getFirstSeries() const
2286 for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
2288 if (!rGroup.empty())
2290 if (!rGroup[0].m_aSeriesVector.empty())
2292 VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
2293 if (pSeries)
2294 return pSeries;
2298 return nullptr;
2301 OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
2303 if (m_pExplicitCategoriesProvider)
2305 Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
2306 if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
2308 return aCategories[nPointIndex];
2311 return OUString();
2314 uno::Sequence< OUString > VSeriesPlotter::getSeriesNames() const
2316 std::vector<OUString> aRetVector;
2318 OUString aRole;
2319 if( m_xChartTypeModel.is() )
2320 aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
2322 for (auto const& rGroup : m_aZSlots)
2324 if (!rGroup.empty())
2326 VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
2327 if (!rSeriesGroup.m_aSeriesVector.empty())
2329 VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
2330 uno::Reference< XDataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
2331 if( xSeries.is() )
2333 OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
2334 aRetVector.push_back( aSeriesName );
2339 return comphelper::containerToSequence( aRetVector );
2342 void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
2344 m_aPageReferenceSize = rPageRefSize;
2346 // set reference size also at all data series
2348 for (auto const & outer : m_aZSlots)
2349 for (VDataSeriesGroup const & rGroup : outer)
2351 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2353 pSeries->setPageReferenceSize(m_aPageReferenceSize);
2358 //better performance for big data
2359 void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
2361 m_aCoordinateSystemResolution = rCoordinateSystemResolution;
2364 bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
2366 return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
2369 bool VSeriesPlotter::shouldSnapRectToUsedArea()
2371 return m_nDimension != 3;
2374 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
2375 const awt::Size& rEntryKeyAspectRatio
2376 , LegendPosition eLegendPosition
2377 , const Reference< beans::XPropertySet >& xTextProperties
2378 , const Reference< drawing::XShapes >& xTarget
2379 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2380 , const Reference< uno::XComponentContext >& xContext
2381 , ChartModel& rModel
2384 std::vector< ViewLegendEntry > aResult;
2386 if( xTarget.is() )
2388 uno::Reference< XCoordinateSystemContainer > xCooSysCnt( rModel.getFirstDiagram(), uno::UNO_QUERY );
2389 Reference< chart2::XCoordinateSystem > xCooSys(xCooSysCnt->getCoordinateSystems()[0]);
2390 Reference< beans::XPropertySet > xProp( xCooSys, uno::UNO_QUERY );
2391 bool bSwapXAndY = false;
2393 if( xProp.is()) try
2395 xProp->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
2397 catch( const uno::Exception& )
2401 //iterate through all series
2402 bool bBreak = false;
2403 bool bFirstSeries = true;
2406 for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
2408 for (VDataSeriesGroup const & rGroup : rGroupVector)
2410 for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
2412 if (!pSeries)
2413 continue;
2415 if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
2417 continue;
2420 std::vector<ViewLegendEntry> aSeriesEntries(
2421 createLegendEntriesForSeries(
2422 rEntryKeyAspectRatio, *pSeries, xTextProperties,
2423 xTarget, xShapeFactory, xContext));
2425 //add series entries to the result now
2427 // use only the first series if VaryColorsByPoint is set for the first series
2428 if (bFirstSeries && pSeries->isVaryColorsByPoint())
2429 bBreak = true;
2430 bFirstSeries = false;
2432 // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
2433 // If the legend is top or bottom and we have a stacked bar-chart the normal order
2434 // is the correct one, unless the chart type is horizontal bar-chart.
2435 bool bReverse = false;
2436 if ( bSwapXAndY )
2438 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2439 bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
2441 else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
2443 StackingDirection eStackingDirection( pSeries->getStackingDirection() );
2444 bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
2447 if (bReverse)
2448 aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
2449 else
2450 aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
2452 if (bBreak)
2453 return aResult;
2458 return aResult;
2461 std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
2463 std::vector<VDataSeries*> aAllSeries;
2464 for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
2466 for(VDataSeriesGroup const & rGroup : rXSlot)
2468 for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
2469 aAllSeries.push_back(p.get());
2472 return aAllSeries;
2475 namespace
2477 bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
2479 bool bHasVisibleLine = false;
2480 rbHasDashedLine = false;
2481 drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
2482 if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
2484 if( aLineStyle != drawing::LineStyle_NONE )
2485 bHasVisibleLine = true;
2486 if( aLineStyle == drawing::LineStyle_DASH )
2487 rbHasDashedLine = true;
2489 return bHasVisibleLine;
2492 bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
2494 bool bHasRegressionCurves = false;
2495 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2496 if( xRegrCont.is())
2498 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves() );
2499 sal_Int32 i = 0, nCount = aCurves.getLength();
2500 for( i=0; i<nCount; ++i )
2502 if( aCurves[i].is() )
2504 bHasRegressionCurves = true;
2505 lcl_HasVisibleLine( uno::Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ), rbHasDashedLine );
2509 return bHasRegressionCurves;
2512 LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
2514 return LegendSymbolStyle::Box;
2517 awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
2519 awt::Size aRet(1000,1000);
2520 if( m_nDimension==3 )
2521 return aRet;
2523 bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
2524 bool bHasLines = false;
2525 bool bHasDashedLines = false;
2526 //iterate through all series
2527 for (VDataSeries* pSeries : getAllSeries())
2529 if( bSeriesAllowsLines )
2531 bool bCurrentDashed = false;
2532 if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
2534 bHasLines = true;
2535 if( bCurrentDashed )
2537 bHasDashedLines = true;
2538 break;
2542 bool bRegressionHasDashedLines=false;
2543 if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
2545 bHasLines = true;
2546 if( bRegressionHasDashedLines )
2548 bHasDashedLines = true;
2549 break;
2553 if( bHasLines )
2555 if( bHasDashedLines )
2556 aRet = awt::Size(1600,-1);
2557 else
2558 aRet = awt::Size(800,-1);
2560 return aRet;
2563 uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
2565 return uno::Any();
2568 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForSeries(
2569 const awt::Size& rEntryKeyAspectRatio
2570 , const VDataSeries& rSeries
2571 , const Reference< drawing::XShapes >& xTarget
2572 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2575 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2576 uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
2578 VLegendSymbolFactory::PropertyType ePropType =
2579 VLegendSymbolFactory::PropertyType::FilledSeries;
2581 // todo: maybe the property-style does not solely depend on the
2582 // legend-symbol type
2583 switch( eLegendSymbolStyle )
2585 case LegendSymbolStyle::Line:
2586 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2587 break;
2588 default:
2589 break;
2591 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2592 xTarget, eLegendSymbolStyle, xShapeFactory
2593 , rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol ));
2595 return xShape;
2598 Reference< drawing::XShape > VSeriesPlotter::createLegendSymbolForPoint(
2599 const awt::Size& rEntryKeyAspectRatio
2600 , const VDataSeries& rSeries
2601 , sal_Int32 nPointIndex
2602 , const Reference< drawing::XShapes >& xTarget
2603 , const Reference< lang::XMultiServiceFactory >& xShapeFactory )
2606 LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
2607 uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
2609 VLegendSymbolFactory::PropertyType ePropType =
2610 VLegendSymbolFactory::PropertyType::FilledSeries;
2612 // todo: maybe the property-style does not solely depend on the
2613 // legend-symbol type
2614 switch( eLegendSymbolStyle )
2616 case LegendSymbolStyle::Line:
2617 ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
2618 break;
2619 default:
2620 break;
2623 // the default properties for the data point are the data series properties.
2624 // If a data point has own attributes overwrite them
2625 Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
2626 Reference< beans::XPropertySet > xPointSet( xSeriesProps );
2627 if( rSeries.isAttributedDataPoint( nPointIndex ) )
2628 xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
2630 // if a data point has no own color use a color from the diagram's color scheme
2631 if( ! rSeries.hasPointOwnColor( nPointIndex ))
2633 Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
2634 if( xCloneable.is() && m_xColorScheme.is() )
2636 xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
2637 Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
2638 if( xChild.is())
2639 xChild->setParent( xSeriesProps );
2641 OSL_ASSERT( xPointSet.is());
2642 xPointSet->setPropertyValue(
2643 "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
2647 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2648 xTarget, eLegendSymbolStyle, xShapeFactory, xPointSet, ePropType, aExplicitSymbol ));
2650 return xShape;
2653 std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
2654 const awt::Size& rEntryKeyAspectRatio
2655 , const VDataSeries& rSeries
2656 , const Reference< beans::XPropertySet >& xTextProperties
2657 , const Reference< drawing::XShapes >& xTarget
2658 , const Reference< lang::XMultiServiceFactory >& xShapeFactory
2659 , const Reference< uno::XComponentContext >& xContext
2662 std::vector< ViewLegendEntry > aResult;
2664 if( ! ( xShapeFactory.is() && xTarget.is() && xContext.is() ) )
2665 return aResult;
2669 ViewLegendEntry aEntry;
2670 OUString aLabelText;
2671 bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
2672 bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
2673 CHART2_SERVICE_NAME_CHARTTYPE_PIE);
2676 if (bIsPie && m_xChartTypeModelProps.is())
2678 bool bDonut = false;
2679 if ((m_xChartTypeModelProps->getPropertyValue("UseRings") >>= bDonut) && bDonut)
2680 bIsPie = false;
2683 catch (const uno::Exception&)
2687 if (bVaryColorsByPoint || bIsPie)
2689 Sequence< OUString > aCategoryNames;
2690 if( m_pExplicitCategoriesProvider )
2691 aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
2692 Sequence<sal_Int32> deletedLegendEntries;
2695 rSeries.getPropertiesOfSeries()->getPropertyValue("DeletedLegendEntries") >>= deletedLegendEntries;
2697 catch (const uno::Exception&)
2700 for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
2702 bool deletedLegendEntry = false;
2703 for (auto& deletedLegendEntryIdx : deletedLegendEntries)
2705 if (nIdx == deletedLegendEntryIdx)
2707 deletedLegendEntry = true;
2708 break;
2711 if (deletedLegendEntry)
2712 continue;
2714 // symbol
2715 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2717 // create the symbol
2718 Reference< drawing::XShape > xShape( createLegendSymbolForPoint( rEntryKeyAspectRatio,
2719 rSeries, nIdx, xSymbolGroup, xShapeFactory ) );
2721 // set CID to symbol for selection
2722 if( xShape.is() )
2724 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2726 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
2727 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2728 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2729 ShapeFactory::setShapeName( xShape, aCID );
2732 // label
2733 aLabelText = aCategoryNames[nIdx];
2734 if( xShape.is() || !aLabelText.isEmpty() )
2736 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2737 aResult.push_back(aEntry);
2741 else
2743 // symbol
2744 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2746 // create the symbol
2747 Reference< drawing::XShape > xShape( createLegendSymbolForSeries(
2748 rEntryKeyAspectRatio, rSeries, xSymbolGroup, xShapeFactory ) );
2750 // set CID to symbol for selection
2751 if( xShape.is())
2753 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2755 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2756 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2757 ShapeFactory::setShapeName( xShape, aCID );
2760 // label
2761 aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
2762 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
2764 aResult.push_back(aEntry);
2767 // don't show legend entry of regression curve & friends if this type of chart
2768 // doesn't support statistics #i63016#, fdo#37197
2769 if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
2770 return aResult;
2772 Reference< XRegressionCurveContainer > xRegrCont( rSeries.getModel(), uno::UNO_QUERY );
2773 if( xRegrCont.is())
2775 Sequence< Reference< XRegressionCurve > > aCurves( xRegrCont->getRegressionCurves());
2776 sal_Int32 i = 0, nCount = aCurves.getLength();
2777 for( i=0; i<nCount; ++i )
2779 if( aCurves[i].is() )
2781 //label
2782 OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
2783 replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
2784 aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
2786 // symbol
2787 uno::Reference< drawing::XShapes > xSymbolGroup( ShapeFactory::getOrCreateShapeFactory(xShapeFactory)->createGroup2D( xTarget ));
2789 // create the symbol
2790 Reference< drawing::XShape > xShape( VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
2791 xSymbolGroup, LegendSymbolStyle::Line, xShapeFactory,
2792 Reference< beans::XPropertySet >( aCurves[i], uno::UNO_QUERY ),
2793 VLegendSymbolFactory::PropertyType::Line, uno::Any() ));
2795 // set CID to symbol for selection
2796 if( xShape.is())
2798 aEntry.aSymbol.set( xSymbolGroup, uno::UNO_QUERY );
2800 bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
2801 ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
2802 OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
2803 aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
2804 OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
2805 ShapeFactory::setShapeName( xShape, aCID );
2808 aResult.push_back(aEntry);
2813 catch( const uno::Exception & )
2815 DBG_UNHANDLED_EXCEPTION("chart2" );
2817 return aResult;
2820 VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
2821 const uno::Reference<XChartType>& xChartTypeModel
2822 , sal_Int32 nDimensionCount
2823 , bool bExcludingPositioning )
2825 if (!xChartTypeModel.is())
2826 return nullptr;
2828 OUString aChartType = xChartTypeModel->getChartType();
2830 VSeriesPlotter* pRet=nullptr;
2831 if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
2832 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2833 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
2834 pRet = new BarChart(xChartTypeModel,nDimensionCount);
2835 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
2836 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
2837 else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
2838 pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
2839 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
2840 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2841 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
2842 pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
2843 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
2844 pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
2845 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
2846 pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
2847 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
2848 pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
2849 else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
2850 pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
2851 else
2852 pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
2853 return pRet;
2856 } //namespace chart
2858 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */