LibreOffice Module chart2 (master) 1
AreaChart.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include "AreaChart.hxx"
22#include <ShapeFactory.hxx>
23#include <CommonConverters.hxx>
25#include <ObjectIdentifier.hxx>
26#include "Splines.hxx"
27#include <ChartType.hxx>
28#include <ChartTypeHelper.hxx>
30#include <Clipping.hxx>
31#include <Stripe.hxx>
32#include <DateHelper.hxx>
33#include <unonames.hxx>
34
35#include <com/sun/star/chart2/Symbol.hpp>
36#include <com/sun/star/chart/DataLabelPlacement.hpp>
37#include <com/sun/star/chart/MissingValueTreatment.hpp>
38
39#include <sal/log.hxx>
40#include <o3tl/safeint.hxx>
41#include <osl/diagnose.h>
42
43#include <com/sun/star/beans/XPropertySet.hpp>
44#include <officecfg/Office/Compatibility.hxx>
45#include <officecfg/Office/Chart.hxx>
46
47#include <limits>
48
49namespace chart
50{
51using namespace ::com::sun::star;
52using namespace ::com::sun::star::chart2;
53
55 , sal_Int32 nDimensionCount
56 , bool bCategoryXAxis
57 , bool bNoArea
58 )
59 : VSeriesPlotter( xChartTypeModel, nDimensionCount, bCategoryXAxis )
60 , m_pMainPosHelper(new PlottingPositionHelper())
61 , m_bArea(!bNoArea)
62 , m_bLine(bNoArea)
63 , m_bSymbol( ChartTypeHelper::isSupportingSymbolProperties(xChartTypeModel,nDimensionCount) )
64 , m_eCurveStyle(CurveStyle_LINES)
65 , m_nCurveResolution(20)
66 , m_nSplineOrder(3)
67{
68 m_pMainPosHelper->AllowShiftXAxisPos(true);
69 m_pMainPosHelper->AllowShiftZAxisPos(true);
70
71 PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
72 VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
73
74 try
75 {
76 if( m_xChartTypeModel.is() )
77 {
78 m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_STYLE) >>= m_eCurveStyle;
79 m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_RESOLUTION) >>= m_nCurveResolution;
80 m_xChartTypeModel->getPropertyValue(CHART_UNONAME_SPLINE_ORDER) >>= m_nSplineOrder;
81 }
82 }
83 catch( uno::Exception& e )
84 {
85 //the above properties are not supported by all charttypes supported by this class (e.g. area or net chart)
86 //in that cases this exception is ok
87 e.Context.is();//to have debug information without compilation warnings
88 }
89}
90
91AreaChart::~AreaChart()
92{
93}
94
95bool AreaChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
96{
97 // no separate stacking in all types of line/area charts
98 return false;
99}
100
101LegendSymbolStyle AreaChart::getLegendSymbolStyle()
102{
103 if( m_bArea || m_nDimension == 3 )
104 return LegendSymbolStyle::Box;
105 return LegendSymbolStyle::Line;
106}
107
108uno::Any AreaChart::getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex )
109{
110 uno::Any aRet;
111
112 Symbol* pSymbolProperties = rSeries.getSymbolProperties( nPointIndex );
113 if( pSymbolProperties )
114 {
115 aRet <<= *pSymbolProperties;
116 }
117
118 return aRet;
119}
120
121drawing::Direction3D AreaChart::getPreferredDiagramAspectRatio() const
122{
123 drawing::Direction3D aRet(1,-1,1);
124 if( m_nDimension == 2 )
125 aRet = drawing::Direction3D(-1,-1,-1);
126 else if (m_pPosHelper)
127 {
128 drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
129 aRet.DirectionZ = aScale.DirectionZ*0.2;
130 if(aRet.DirectionZ>1.0)
131 aRet.DirectionZ=1.0;
132 if(aRet.DirectionZ>10)
133 aRet.DirectionZ=10;
134 }
135 return aRet;
136}
137
138void AreaChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
139{
140 if( m_bArea && pSeries )
141 {
142 sal_Int32 nMissingValueTreatment = pSeries->getMissingValueTreatment();
143 if( nMissingValueTreatment == css::chart::MissingValueTreatment::LEAVE_GAP )
144 pSeries->setMissingValueTreatment( css::chart::MissingValueTreatment::USE_ZERO );
145 }
146 if( m_nDimension == 3 && !m_bCategoryXAxis )
147 {
148 //3D xy always deep
149 OSL_ENSURE( zSlot==-1,"3D xy charts should be deep stacked in model also" );
150 zSlot=-1;
151 xSlot=0;
152 ySlot=0;
153 }
154 VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
155}
156
157static void lcl_removeDuplicatePoints( std::vector<std::vector<css::drawing::Position3D>>& rPolyPoly, PlottingPositionHelper& rPosHelper )
158{
159 sal_Int32 nPolyCount = rPolyPoly.size();
160 if(!nPolyCount)
161 return;
162
163 // TODO we could do with without a temporary array
164 std::vector<std::vector<css::drawing::Position3D>> aTmp;
165 aTmp.resize(nPolyCount);
166
167 for( sal_Int32 nPolygonIndex = 0; nPolygonIndex<nPolyCount; nPolygonIndex++ )
168 {
169 std::vector<css::drawing::Position3D>* pOuterSource = &rPolyPoly[nPolygonIndex];
170 std::vector<css::drawing::Position3D>* pOuterTarget = &aTmp[nPolygonIndex];
171
172 sal_Int32 nPointCount = pOuterSource->size();
173 if( !nPointCount )
174 continue;
175
176 pOuterTarget->resize(nPointCount);
177
178 css::drawing::Position3D* pSource = pOuterSource->data();
179 css::drawing::Position3D* pTarget = pOuterTarget->data();
180
181 //copy first point
182 *pTarget=*pSource++;
183 sal_Int32 nTargetPointCount=1;
184
185 for( sal_Int32 nSource=1; nSource<nPointCount; nSource++ )
186 {
187 if( !rPosHelper.isSameForGivenResolution( pTarget->PositionX, pTarget->PositionY, pTarget->PositionZ
188 , pSource->PositionX, pSource->PositionY, pSource->PositionZ ) )
189 {
190 pTarget++;
191 *pTarget=*pSource;
192 nTargetPointCount++;
193 }
194 pSource++;
195 }
196
197 //free unused space
198 if( nTargetPointCount<nPointCount )
199 {
200 pOuterTarget->resize(nTargetPointCount);
201 }
202
203 pOuterSource->clear();
204 }
205
206 //free space
207 rPolyPoly.resize(nPolyCount);
208
209 rPolyPoly = std::move(aTmp);
210}
211
212bool AreaChart::create_stepped_line(
213 std::vector<std::vector<css::drawing::Position3D>> aStartPoly,
214 chart2::CurveStyle eCurveStyle,
215 PlottingPositionHelper const * pPosHelper,
216 std::vector<std::vector<css::drawing::Position3D>> &aPoly )
217{
218 sal_uInt32 nOuterCount = aStartPoly.size();
219 if ( !nOuterCount )
220 return false;
221
222 std::vector<std::vector<css::drawing::Position3D>> aSteppedPoly;
223 aSteppedPoly.resize(nOuterCount);
224
225 auto pSequence = aSteppedPoly.data();
226
227 for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
228 {
229 if( aStartPoly[nOuter].size() <= 1 )
230 continue; //we need at least two points
231
232 sal_uInt32 nMaxIndexPoints = aStartPoly[nOuter].size()-1; // is >1
233 sal_uInt32 nNewIndexPoints = 0;
234 if ( eCurveStyle==CurveStyle_STEP_START || eCurveStyle==CurveStyle_STEP_END)
235 nNewIndexPoints = nMaxIndexPoints * 2 + 1;
236 else
237 nNewIndexPoints = nMaxIndexPoints * 3 + 1;
238
239 const css::drawing::Position3D* pOld = aStartPoly[nOuter].data();
240
241 pSequence[nOuter].resize( nNewIndexPoints );
242
243 css::drawing::Position3D* pNew = pSequence[nOuter].data();
244
245 pNew[0] = pOld[0];
246 for( sal_uInt32 oi = 0; oi < nMaxIndexPoints; oi++ )
247 {
248 switch ( eCurveStyle )
249 {
250 case CurveStyle_STEP_START:
257 // create the intermediate point
258 pNew[1+oi*2].PositionX = pOld[oi+1].PositionX;
259 pNew[1+oi*2].PositionY = pOld[oi].PositionY;
260 pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
261 // and now the normal one
262 pNew[1+oi*2+1] = pOld[oi+1];
263 break;
264 case CurveStyle_STEP_END:
271 // create the intermediate point
272 pNew[1+oi*2].PositionX = pOld[oi].PositionX;
273 pNew[1+oi*2].PositionY = pOld[oi+1].PositionY;
274 pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
275 // and now the normal one
276 pNew[1+oi*2+1] = pOld[oi+1];
277 break;
278 case CurveStyle_STEP_CENTER_X:
285 // create the first intermediate point
286 pNew[1+oi*3].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
287 pNew[1+oi*3].PositionY = pOld[oi].PositionY;
288 pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
289 // create the second intermediate point
290 pNew[1+oi*3+1].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
291 pNew[1+oi*3+1].PositionY = pOld[oi+1].PositionY;
292 pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
293 // and now the normal one
294 pNew[1+oi*3+2] = pOld[oi+1];
295 break;
296 case CurveStyle_STEP_CENTER_Y:
303 // create the first intermediate point
304 pNew[1+oi*3].PositionX = pOld[oi].PositionX;
305 pNew[1+oi*3].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
306 pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
307 // create the second intermediate point
308 pNew[1+oi*3+1].PositionX = pOld[oi+1].PositionX;
309 pNew[1+oi*3+1].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
310 pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
311 // and now the normal one
312 pNew[1+oi*3+2] = pOld[oi+1];
313 break;
314 default:
315 // this should never be executed
316 OSL_FAIL("Unknown curvestyle in AreaChart::create_stepped_line");
317 }
318 }
319 }
320 Clipping::clipPolygonAtRectangle( aSteppedPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
321
322 return true;
323}
324
325bool AreaChart::impl_createLine( VDataSeries* pSeries
326 , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
327 , PlottingPositionHelper* pPosHelper )
328{
329 //return true if a line was created successfully
330 rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
331
332 std::vector<std::vector<css::drawing::Position3D>> aPoly;
333 if(m_eCurveStyle==CurveStyle_CUBIC_SPLINES)
334 {
335 std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
336 SplineCalculater::CalculateCubicSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution );
337 lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
338 Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
339 }
340 else if(m_eCurveStyle==CurveStyle_B_SPLINES)
341 {
342 std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
343 SplineCalculater::CalculateBSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution, m_nSplineOrder );
344 lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
345 Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
346 }
347 else if (m_eCurveStyle==CurveStyle_STEP_START ||
348 m_eCurveStyle==CurveStyle_STEP_END ||
349 m_eCurveStyle==CurveStyle_STEP_CENTER_Y ||
350 m_eCurveStyle==CurveStyle_STEP_CENTER_X
351 )
352 {
353 if (!create_stepped_line(*pSeriesPoly, m_eCurveStyle, pPosHelper, aPoly))
354 {
355 return false;
356 }
357 }
358 else
359 { // default to creating a straight line
360 SAL_WARN_IF(m_eCurveStyle != CurveStyle_LINES, "chart2.areachart", "Unknown curve style");
361 Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
362 }
363
364 if(!ShapeFactory::hasPolygonAnyLines(aPoly))
365 return false;
366
367 //transformation 3) -> 4)
368 pPosHelper->transformScaledLogicToScene( aPoly );
369
370 //create line:
372 if(m_nDimension==3)
373 {
374 double fDepth = getTransformedDepth();
375 sal_Int32 nPolyCount = aPoly.size();
376 for(sal_Int32 nPoly=0;nPoly<nPolyCount;nPoly++)
377 {
378 sal_Int32 nPointCount = aPoly[nPoly].size();
379 for(sal_Int32 nPoint=0;nPoint<nPointCount-1;nPoint++)
380 {
381 drawing::Position3D aPoint1, aPoint2;
382 aPoint1 = aPoly[nPoly][nPoint+1];
383 aPoint2 = aPoly[nPoly][nPoint];
384
385 ShapeFactory::createStripe(xSeriesGroupShape_Shapes
386 , Stripe( aPoint1, aPoint2, fDepth )
387 , pSeries->getPropertiesOfSeries(), PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), true, 1 );
388 }
389 }
390 }
391 else //m_nDimension!=3
392 {
393 xShape = ShapeFactory::createLine2D( xSeriesGroupShape_Shapes, aPoly );
394 PropertyMapper::setMappedProperties( *xShape
395 , pSeries->getPropertiesOfSeries()
396 , PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
397 //because of this name this line will be used for marking
398 ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
399 }
400 return true;
401}
402
403bool AreaChart::impl_createArea( VDataSeries* pSeries
404 , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
405 , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
406 , PlottingPositionHelper const * pPosHelper )
407{
408 //return true if an area was created successfully
409
410 rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
411 double zValue = pSeries->m_fLogicZPos;
412
413 std::vector<std::vector<css::drawing::Position3D>> aPoly( *pSeriesPoly );
414 //add second part to the polygon (grounding points or previous series points)
415 if(!pPreviousSeriesPoly)
416 {
417 double fMinX = pSeries->m_fLogicMinX;
418 double fMaxX = pSeries->m_fLogicMaxX;
419 double fY = pPosHelper->getBaseValueY();//logic grounding
420 if( m_nDimension==3 )
421 fY = pPosHelper->getLogicMinY();
422
423 //clip to scale
424 if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX())
425 return false;//no visible shape needed
426 pPosHelper->clipLogicValues( &fMinX, &fY, nullptr );
427 pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr );
428
429 //apply scaling
430 {
431 pPosHelper->doLogicScaling( &fMinX, &fY, &zValue );
432 pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr );
433 }
434
435 AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) );
436 AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) );
437 }
438 else
439 {
440 appendPoly( aPoly, *pPreviousSeriesPoly );
441 }
442 ShapeFactory::closePolygon(aPoly);
443
444 //apply clipping
445 {
446 std::vector<std::vector<css::drawing::Position3D>> aClippedPoly;
447 Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false );
448 ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping
449 aPoly = aClippedPoly;
450 }
451
452 if(!ShapeFactory::hasPolygonAnyLines(aPoly))
453 return false;
454
455 //transformation 3) -> 4)
456 pPosHelper->transformScaledLogicToScene( aPoly );
457
458 //create area:
460 if(m_nDimension==3)
461 {
462 xShape = ShapeFactory::createArea3D( xSeriesGroupShape_Shapes
463 , aPoly, getTransformedDepth() );
464 }
465 else //m_nDimension!=3
466 {
467 xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes
468 , aPoly );
469 }
470 PropertyMapper::setMappedProperties( *xShape
471 , pSeries->getPropertiesOfSeries()
472 , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
473 //because of this name this line will be used for marking
474 ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
475 return true;
476}
477
478void AreaChart::impl_createSeriesShapes()
479{
480 //the polygon shapes for each series need to be created before
481
482 //iterate through all series again to create the series shapes
483 for( auto const& rZSlot : m_aZSlots )
484 {
485 for( auto const& rXSlot : rZSlot )
486 {
487 std::map< sal_Int32, std::vector<std::vector<css::drawing::Position3D>>* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex
488 std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = nullptr;
489
490 //iterate through all series
491 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
492 {
493 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
494 PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
495 m_pPosHelper = &rPosHelper;
496
497 createRegressionCurvesShapes( *pSeries, m_xErrorBarTarget, m_xRegressionCurveEquationTarget,
498 m_pPosHelper->maySkipPointsInRegressionCalculation());
499
500 pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
501 if( m_bArea )
502 {
503 if (!impl_createArea(pSeries.get(), pSeriesPoly,
504 aPreviousSeriesPolyMap[nAttachedAxisIndex], &rPosHelper))
505 continue;
506 }
507 if( m_bLine )
508 {
509 if (!impl_createLine(pSeries.get(), pSeriesPoly, &rPosHelper))
510 continue;
511 }
512 aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly;
513 }//next series in x slot (next y slot)
514 }//next x slot
515 }//next z slot
516}
517
518namespace
519{
520
521void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots )
522{
523 std::vector< std::vector< VDataSeriesGroup > > aRet;
524 aRet.reserve( rZSlots.size() );
525
526 std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() );
527 std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() );
528 for( ; aZIt != aZEnd; ++aZIt )
529 {
530 std::vector< VDataSeriesGroup > aXSlot;
531 aXSlot.reserve( aZIt->size() );
532
533 std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() );
534 std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() );
535 for( ; aXIt != aXEnd; ++aXIt )
536 aXSlot.push_back(std::move(*aXIt));
537
538 aRet.push_back(std::move(aXSlot));
539 }
540
541 rZSlots = std::move(aRet);
542}
543
544//better performance for big data
545struct FormerPoint
546{
547 FormerPoint( double fX, double fY, double fZ )
548 : m_fX(fX), m_fY(fY), m_fZ(fZ)
549 {}
550 FormerPoint()
551 : m_fX(std::numeric_limits<double>::quiet_NaN())
552 , m_fY(std::numeric_limits<double>::quiet_NaN())
553 , m_fZ(std::numeric_limits<double>::quiet_NaN())
554 {
555 }
556
557 double m_fX;
558 double m_fY;
559 double m_fZ;
560};
561
562}//anonymous namespace
563
564void AreaChart::createShapes()
565{
566 if( m_aZSlots.empty() ) //no series
567 return;
568
569 //tdf#127813 Don't reverse the series in OOXML-heavy environments
570 if( officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_nDimension == 2 && ( m_bArea || !m_bCategoryXAxis ) )
571 lcl_reorderSeries( m_aZSlots );
572
573 OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"AreaChart is not proper initialized");
574 if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
575 return;
576
577 //the text labels should be always on top of the other series shapes
578 //for area chart the error bars should be always on top of the other series shapes
579
580 //therefore create an own group for the texts and the error bars to move them to front
581 //(because the text group is created after the series group the texts are displayed on top)
582 m_xSeriesTarget = createGroupShape( m_xLogicTarget );
583 if( m_bArea )
584 m_xErrorBarTarget = createGroupShape( m_xLogicTarget );
585 else
586 m_xErrorBarTarget = m_xSeriesTarget;
587 m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
588 m_xRegressionCurveEquationTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
589
590 //check necessary here that different Y axis can not be stacked in the same group? ... hm?
591
592 //update/create information for current group
593 double fLogicZ = 1.0;//as defined
594
595 sal_Int32 nStartIndex = 0; // inclusive ;..todo get somehow from x scale
596 sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
597 if(nEndIndex<=0)
598 nEndIndex=1;
599
600 //better performance for big data
601 std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
602 m_bPointsWereSkipped = false;
603 sal_Int32 nSkippedPoints = 0;
604 sal_Int32 nCreatedPoints = 0;
605
606 bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis());
607
608 std::vector<std::map< sal_Int32, double > > aLogicYSumMapByX(nEndIndex);//one for each different nAttachedAxisIndex
609 for( auto const& rZSlot : m_aZSlots )
610 {
611 //iterate through all x slots in this category to get 100percent sum
612 for( auto const& rXSlot : rZSlot )
613 {
614 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
615 {
616 if(!pSeries)
617 continue;
618
619 if (bDateCategory)
620 pSeries->doSortByXValues();
621
622 for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
623 {
624 std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
625 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
626 rLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
627
628 m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
629
630 double fAdd = pSeries->getYValue( nIndex );
631 if( !std::isnan(fAdd) && !std::isinf(fAdd) )
632 rLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd );
633 }
634 }
635 }
636 }
637
638 const bool bUseErrorRectangle = officecfg::Office::Chart::ErrorProperties::ErrorRectangle::get();
639
640 sal_Int32 nZ=1;
641 for( auto const& rZSlot : m_aZSlots )
642 {
643 //for the area chart there should be at most one x slot (no side by side stacking available)
644 //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not???
645 for( auto const& rXSlot : rZSlot )
646 {
647 std::vector<std::map< sal_Int32, double > > aLogicYForNextSeriesMapByX(nEndIndex); //one for each different nAttachedAxisIndex
648 //iterate through all series
649 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
650 {
651 if(!pSeries)
652 continue;
653
654 rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget);
655
656 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
657 double fXMin, fXMax;
658 pSeries->getMinMaxXValue(fXMin, fXMax);
659 PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
660 m_pPosHelper = &rPosHelper;
661
662 if(m_nDimension==3)
663 fLogicZ = nZ+0.5;
664 pSeries->m_fLogicZPos = fLogicZ;
665
666 for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
667 {
668
669 /* #i70133# ignore points outside of series length in standard area
670 charts. Stacked area charts will use missing points as zeros. In
671 standard charts, pSeriesList contains only one series. */
672 if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) )
673 continue;
674
675 //collect data point information (logic coordinates, style ):
676 double fLogicX = pSeries->getXValue(nIndex);
677 if (bDateCategory)
678 {
679 if (std::isnan(fLogicX) || (fLogicX < fXMin || fLogicX > fXMax))
680 continue;
681
682 fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution );
683 }
684 double fLogicY = pSeries->getYValue(nIndex);
685
686 if( m_nDimension==3 && m_bArea && rXSlot.m_aSeriesVector.size()!=1 )
687 fLogicY = fabs( fLogicY );
688
689 double fLogicValueForLabeDisplay = fLogicY;
690 std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
691 if (rPosHelper.isPercentY() && rLogicYSumMap[nAttachedAxisIndex] != 0.0)
692 {
693 fLogicY = fabs( fLogicY )/rLogicYSumMap[nAttachedAxisIndex];
694 }
695
696 if( std::isnan(fLogicX) || std::isinf(fLogicX)
697 || std::isnan(fLogicY) || std::isinf(fLogicY)
698 || std::isnan(fLogicZ) || std::isinf(fLogicZ) )
699 {
700 if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
701 {
702 std::vector<std::vector<css::drawing::Position3D>>& rPolygon = pSeries->m_aPolyPolygonShape3D;
703 sal_Int32& rIndex = pSeries->m_nPolygonIndex;
704 if( 0<= rIndex && o3tl::make_unsigned(rIndex) < rPolygon.size() )
705 {
706 if( !rPolygon[ rIndex ].empty() )
707 rIndex++; //start a new polygon for the next point if the current poly is not empty
708 }
709 }
710 continue;
711 }
712
713 std::map< sal_Int32, double >& rLogicYForNextSeriesMap = aLogicYForNextSeriesMapByX[nIndex];
714 rLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0);
715
716 double fPreviousYValue = rLogicYForNextSeriesMap[nAttachedAxisIndex];
717 fLogicY += rLogicYForNextSeriesMap[nAttachedAxisIndex];
718 rLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY;
719
720 bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ);
721
722 //remind minimal and maximal x values for area 'grounding' points
723 //only for filled area
724 {
725 double& rfMinX = pSeries->m_fLogicMinX;
726 if(!nIndex||fLogicX<rfMinX)
727 rfMinX=fLogicX;
728 double& rfMaxX = pSeries->m_fLogicMaxX;
729 if(!nIndex||fLogicX>rfMaxX)
730 rfMaxX=fLogicX;
731 }
732
733 drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
734 drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
735 rPosHelper.doLogicScaling(aScaledLogicPosition);
736
737 //transformation 3) -> 4)
738 drawing::Position3D aScenePosition(
739 rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
740
741 //better performance for big data
742 FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
743 rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution);
744 if (!pSeries->isAttributedDataPoint(nIndex)
745 && rPosHelper.isSameForGivenResolution(
746 aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ,
747 aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY,
748 aScaledLogicPosition.PositionZ))
749 {
750 ++nSkippedPoints;
751 m_bPointsWereSkipped = true;
752 continue;
753 }
754 aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
755
756 //store point information for series polygon
757 //for area and/or line (symbols only do not need this)
758 if( isValidPosition(aScaledLogicPosition) )
759 {
760 AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
761 }
762
763 //create a single datapoint if point is visible
764 //apply clipping:
765 if( !bIsVisible )
766 continue;
767
768 bool bCreateYErrorBar = false, bCreateXErrorBar = false;
769 {
770 uno::Reference< beans::XPropertySet > xErrorBarProp(pSeries->getYErrorBarProperties(nIndex));
771 if( xErrorBarProp.is() )
772 {
773 bool bShowPositive = false;
774 bool bShowNegative = false;
775 xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
776 xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
777 bCreateYErrorBar = bShowPositive || bShowNegative;
778 }
779
780 xErrorBarProp = pSeries->getXErrorBarProperties(nIndex);
781 if ( xErrorBarProp.is() )
782 {
783 bool bShowPositive = false;
784 bool bShowNegative = false;
785 xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
786 xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
787 bCreateXErrorBar = bShowPositive || bShowNegative;
788 }
789 }
790
791 Symbol* pSymbolProperties = m_bSymbol ? pSeries->getSymbolProperties( nIndex ) : nullptr;
792 bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE);
793
794 if( !bCreateSymbol && !bCreateYErrorBar &&
795 !bCreateXErrorBar && !pSeries->getDataPointLabelIfLabel(nIndex) )
796 continue;
797
798 {
799 nCreatedPoints++;
800
801 //create data point
802 drawing::Direction3D aSymbolSize(0,0,0);
803 if( bCreateSymbol )
804 {
805 if(m_nDimension!=3)
806 {
807 //create a group shape for this point and add to the series shape:
808 OUString aPointCID = ObjectIdentifier::createPointCID(
809 pSeries->getPointCID_Stub(), nIndex );
810 rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes;
811 if (pSymbolProperties->Style == SymbolStyle_STANDARD || pSymbolProperties->Style == SymbolStyle_GRAPHIC)
812 xPointGroupShape_Shapes = createGroupShape(xSeriesGroupShape_Shapes,aPointCID);
813
814 if (pSymbolProperties->Style != SymbolStyle_NONE)
815 {
816 aSymbolSize.DirectionX = pSymbolProperties->Size.Width;
817 aSymbolSize.DirectionY = pSymbolProperties->Size.Height;
818 }
819
820 if (pSymbolProperties->Style == SymbolStyle_STANDARD)
821 {
822 sal_Int32 nSymbol = pSymbolProperties->StandardSymbol;
823 ShapeFactory::createSymbol2D(
824 xPointGroupShape_Shapes, aScenePosition, aSymbolSize,
825 nSymbol, pSymbolProperties->BorderColor,
826 pSymbolProperties->FillColor);
827 }
828 else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC)
829 {
830 ShapeFactory::createGraphic2D(xPointGroupShape_Shapes,
831 aScenePosition, aSymbolSize,
832 pSymbolProperties->Graphic);
833 }
834 //@todo other symbol styles
835 }
836 }
837 //create error bars or rectangles, depending on configuration
838 if ( bUseErrorRectangle )
839 {
840 if ( bCreateXErrorBar || bCreateYErrorBar )
841 {
842 createErrorRectangle(
843 aUnscaledLogicPosition,
844 *pSeries,
845 nIndex,
846 m_xErrorBarTarget,
847 bCreateXErrorBar,
848 bCreateYErrorBar );
849 }
850 }
851 else
852 {
853 if (bCreateXErrorBar)
854 createErrorBar_X( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget );
855
856 if (bCreateYErrorBar)
857 createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget, nullptr );
858 }
859
860 //create data point label
861 if( pSeries->getDataPointLabelIfLabel(nIndex) )
862 {
863 LabelAlignment eAlignment = LABEL_ALIGN_TOP;
864 sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
865 nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY());
866
867 if (m_bArea && nLabelPlacement == css::chart::DataLabelPlacement::CENTER)
868 {
869 if (fPreviousYValue)
870 fLogicY -= (fLogicY - fPreviousYValue) / 2.0;
871 else
872 fLogicY = (fLogicY + rPosHelper.getLogicMinY()) / 2.0;
873 aScenePosition = rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false);
874 }
875
876 drawing::Position3D aScenePosition3D( aScenePosition.PositionX
877 , aScenePosition.PositionY
878 , aScenePosition.PositionZ+getTransformedDepth() );
879
880 switch(nLabelPlacement)
881 {
882 case css::chart::DataLabelPlacement::TOP:
883 aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
884 eAlignment = LABEL_ALIGN_TOP;
885 break;
886 case css::chart::DataLabelPlacement::BOTTOM:
887 aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
888 eAlignment = LABEL_ALIGN_BOTTOM;
889 break;
890 case css::chart::DataLabelPlacement::LEFT:
891 aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
892 eAlignment = LABEL_ALIGN_LEFT;
893 break;
894 case css::chart::DataLabelPlacement::RIGHT:
895 aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
896 eAlignment = LABEL_ALIGN_RIGHT;
897 break;
898 case css::chart::DataLabelPlacement::CENTER:
899 eAlignment = LABEL_ALIGN_CENTER;
900 break;
901 default:
902 OSL_FAIL("this label alignment is not implemented yet");
903 aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
904 eAlignment = LABEL_ALIGN_TOP;
905 break;
906 }
907
908 awt::Point aScreenPosition2D;//get the screen position for the labels
909 sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent
910 {
911 if(eAlignment==LABEL_ALIGN_CENTER || m_nDimension == 3 )
912 nOffset = 0;
913 aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget)
914 .transformSceneToScreenPosition( aScenePosition3D );
915 }
916
917 createDataLabel( m_xTextTarget, *pSeries, nIndex
918 , fLogicValueForLabeDisplay
919 , rLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset );
920 }
921 }
922 }
923
924 }//next series in x slot (next y slot)
925 }//next x slot
926 ++nZ;
927 }//next z slot
928
929 impl_createSeriesShapes();
930
931 /* @todo remove series shapes if empty
932 //remove and delete point-group-shape if empty
933 if(!xSeriesGroupShape_Shapes->getCount())
934 {
935 pSeries->m_xShape.set(NULL);
936 m_xLogicTarget->remove(xSeriesGroupShape_Shape);
937 }
938 */
939
940 //remove and delete series-group-shape if empty
941
942 //... todo
943
944 SAL_INFO(
945 "chart2",
946 "skipped points: " << nSkippedPoints << " created points: "
947 << nCreatedPoints);
948}
949
950} //namespace chart
951
952/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
double m_fY
Definition: AreaChart.cxx:558
double m_fX
Definition: AreaChart.cxx:557
double m_fZ
Definition: AreaChart.cxx:559
AreaChart()=delete
css::awt::Point transformSceneToScreenPosition(const css::drawing::Position3D &rScenePosition3D) const
bool isSameForGivenResolution(double fX, double fY, double fZ, double fX2, double fY2, double fZ2)
void setCoordinateSystemResolution(const css::uno::Sequence< sal_Int32 > &rCoordinateSystemResolution)
::basegfx::B2DRectangle getScaledLogicClipDoubleRect() const
bool isLogicVisible(double fX, double fY, double fZ) const
virtual css::drawing::Position3D transformLogicToScene(double fX, double fY, double fZ, bool bClip) const
void doLogicScaling(double *pX, double *pY, double *pZ) const
virtual css::drawing::Position3D transformScaledLogicToScene(double fX, double fY, double fZ, bool bClip) const
void clipLogicValues(double *pX, double *pY, double *pZ) const
static void setShapeName(const rtl::Reference< SvxShape > &xShape, const OUString &rName)
A Stripe represents a 2 dimensional foursquare plane in a 3 dimensional room.
Definition: Stripe.hxx:34
css::chart2::Symbol * getSymbolProperties(sal_Int32 index) const
const css::uno::Reference< css::beans::XPropertySet > & getPropertiesOfSeries() const
sal_Int32 nIndex
#define SAL_WARN_IF(condition, area, stream)
#define SAL_INFO(area, stream)
size
OOO_DLLPUBLIC_CHARTTOOLS void AddPointToPoly(css::drawing::PolyPolygonShape3D &rPoly, const css::drawing::Position3D &rPos, sal_Int32 nSequenceIndex=0)
PolyPolygonShape3D + drawing::Position3D -> PolyPolygonShape3D.
OOO_DLLPUBLIC_CHARTTOOLS void appendPoly(std::vector< std::vector< css::drawing::Position3D > > &rRet, const std::vector< std::vector< css::drawing::Position3D > > &rAdd)
PolyPolygonShape3D + PolyPolygonShape3D -> PolyPolygonShape3D.
@ LABEL_ALIGN_CENTER
@ LABEL_ALIGN_TOP
@ LABEL_ALIGN_RIGHT
@ LABEL_ALIGN_LEFT
@ LABEL_ALIGN_BOTTOM
static void lcl_removeDuplicatePoints(std::vector< std::vector< css::drawing::Position3D > > &rPolyPoly, PlottingPositionHelper &rPosHelper)
Definition: AreaChart.cxx:157
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
constexpr OUStringLiteral CHART_UNONAME_SPLINE_ORDER
Definition: unonames.hxx:16
constexpr OUStringLiteral CHART_UNONAME_CURVE_STYLE
Definition: unonames.hxx:18
constexpr OUStringLiteral CHART_UNONAME_CURVE_RESOLUTION
Definition: unonames.hxx:19