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" 21 #include <PlottingPositionHelper.hxx> 22 #include <ShapeFactory.hxx> 23 #include <CommonConverters.hxx> 24 #include <ExplicitCategoriesProvider.hxx> 25 #include <ObjectIdentifier.hxx> 26 #include "Splines.hxx" 27 #include <ChartTypeHelper.hxx> 28 #include <LabelPositionHelper.hxx> 29 #include <Clipping.hxx> 30 #include <Stripe.hxx> 31 #include <DateHelper.hxx> 32 #include <unonames.hxx> 33 #include <ConfigAccess.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 <rtl/math.hxx> 40 #include <sal/log.hxx> 41 #include <osl/diagnose.h> 42 43 #include <com/sun/star/drawing/DoubleSequence.hpp> 44 #include <com/sun/star/drawing/XShapes.hpp> 45 #include <com/sun/star/beans/XPropertySet.hpp> 46 #include <officecfg/Office/Compatibility.hxx> 47 48 namespace chart 49 { 50 using namespace ::com::sun::star; 51 using namespace ::rtl::math; 52 using namespace ::com::sun::star::chart2; 53 54 AreaChart::AreaChart( const uno::Reference<XChartType>& xChartTypeModel 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_xChartTypeModelProps.is() ) 77 { 78 m_xChartTypeModelProps->getPropertyValue(CHART_UNONAME_CURVE_STYLE) >>= m_eCurveStyle; 79 m_xChartTypeModelProps->getPropertyValue(CHART_UNONAME_CURVE_RESOLUTION) >>= m_nCurveResolution; 80 m_xChartTypeModelProps->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 91 AreaChart::~AreaChart() 92 { 93 } 94 95 bool AreaChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ ) 96 { 97 // no separate stacking in all types of line/area charts 98 return false; 99 } 100 101 LegendSymbolStyle AreaChart::getLegendSymbolStyle() 102 { 103 if( m_bArea || m_nDimension == 3 ) 104 return LegendSymbolStyle::Box; 105 return LegendSymbolStyle::Line; 106 } 107 108 uno::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 121 drawing::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 138 void 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 157 static void lcl_removeDuplicatePoints( drawing::PolyPolygonShape3D& rPolyPoly, PlottingPositionHelper& rPosHelper ) 158 { 159 sal_Int32 nPolyCount = rPolyPoly.SequenceX.getLength(); 160 if(!nPolyCount) 161 return; 162 163 drawing::PolyPolygonShape3D aTmp; 164 aTmp.SequenceX.realloc(nPolyCount); 165 aTmp.SequenceY.realloc(nPolyCount); 166 aTmp.SequenceZ.realloc(nPolyCount); 167 168 for( sal_Int32 nPolygonIndex = 0; nPolygonIndex<nPolyCount; nPolygonIndex++ ) 169 { 170 drawing::DoubleSequence* pOuterSourceX = &rPolyPoly.SequenceX.getArray()[nPolygonIndex]; 171 drawing::DoubleSequence* pOuterSourceY = &rPolyPoly.SequenceY.getArray()[nPolygonIndex]; 172 drawing::DoubleSequence* pOuterSourceZ = &rPolyPoly.SequenceZ.getArray()[nPolygonIndex]; 173 174 drawing::DoubleSequence* pOuterTargetX = &aTmp.SequenceX.getArray()[nPolygonIndex]; 175 drawing::DoubleSequence* pOuterTargetY = &aTmp.SequenceY.getArray()[nPolygonIndex]; 176 drawing::DoubleSequence* pOuterTargetZ = &aTmp.SequenceZ.getArray()[nPolygonIndex]; 177 178 sal_Int32 nPointCount = pOuterSourceX->getLength(); 179 if( !nPointCount ) 180 continue; 181 182 pOuterTargetX->realloc(nPointCount); 183 pOuterTargetY->realloc(nPointCount); 184 pOuterTargetZ->realloc(nPointCount); 185 186 double* pSourceX = pOuterSourceX->getArray(); 187 double* pSourceY = pOuterSourceY->getArray(); 188 double* pSourceZ = pOuterSourceZ->getArray(); 189 190 double* pTargetX = pOuterTargetX->getArray(); 191 double* pTargetY = pOuterTargetY->getArray(); 192 double* pTargetZ = pOuterTargetZ->getArray(); 193 194 //copy first point 195 *pTargetX=*pSourceX++; 196 *pTargetY=*pSourceY++; 197 *pTargetZ=*pSourceZ++; 198 sal_Int32 nTargetPointCount=1; 199 200 for( sal_Int32 nSource=1; nSource<nPointCount; nSource++ ) 201 { 202 if( !rPosHelper.isSameForGivenResolution( *pTargetX, *pTargetY, *pTargetZ 203 , *pSourceX, *pSourceY, *pSourceZ ) ) 204 { 205 pTargetX++; pTargetY++; pTargetZ++; 206 *pTargetX=*pSourceX; 207 *pTargetY=*pSourceY; 208 *pTargetZ=*pSourceZ; 209 nTargetPointCount++; 210 } 211 pSourceX++; pSourceY++; pSourceZ++; 212 } 213 214 //free unused space 215 if( nTargetPointCount<nPointCount ) 216 { 217 pOuterTargetX->realloc(nTargetPointCount); 218 pOuterTargetY->realloc(nTargetPointCount); 219 pOuterTargetZ->realloc(nTargetPointCount); 220 } 221 222 pOuterSourceX->realloc(0); 223 pOuterSourceY->realloc(0); 224 pOuterSourceZ->realloc(0); 225 } 226 227 //free space 228 rPolyPoly.SequenceX.realloc(nPolyCount); 229 rPolyPoly.SequenceY.realloc(nPolyCount); 230 rPolyPoly.SequenceZ.realloc(nPolyCount); 231 232 rPolyPoly=aTmp; 233 } 234 235 bool AreaChart::create_stepped_line( drawing::PolyPolygonShape3D aStartPoly, chart2::CurveStyle eCurveStyle, PlottingPositionHelper const * pPosHelper, drawing::PolyPolygonShape3D &aPoly ) 236 { 237 sal_uInt32 nOuterCount = aStartPoly.SequenceX.getLength(); 238 if ( !nOuterCount ) 239 return false; 240 241 drawing::PolyPolygonShape3D aSteppedPoly; 242 aSteppedPoly.SequenceX.realloc(nOuterCount); 243 aSteppedPoly.SequenceY.realloc(nOuterCount); 244 aSteppedPoly.SequenceZ.realloc(nOuterCount); 245 246 for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter ) 247 { 248 if( aStartPoly.SequenceX[nOuter].getLength() <= 1 ) 249 continue; //we need at least two points 250 251 sal_uInt32 nMaxIndexPoints = aStartPoly.SequenceX[nOuter].getLength()-1; // is >1 252 sal_uInt32 nNewIndexPoints = 0; 253 if ( eCurveStyle==CurveStyle_STEP_START || eCurveStyle==CurveStyle_STEP_END) 254 nNewIndexPoints = nMaxIndexPoints * 2 + 1; 255 else 256 nNewIndexPoints = nMaxIndexPoints * 3 + 1; 257 258 const double* pOldX = aStartPoly.SequenceX[nOuter].getConstArray(); 259 const double* pOldY = aStartPoly.SequenceY[nOuter].getConstArray(); 260 const double* pOldZ = aStartPoly.SequenceZ[nOuter].getConstArray(); 261 262 aSteppedPoly.SequenceX[nOuter].realloc( nNewIndexPoints ); 263 aSteppedPoly.SequenceY[nOuter].realloc( nNewIndexPoints ); 264 aSteppedPoly.SequenceZ[nOuter].realloc( nNewIndexPoints ); 265 266 double* pNewX = aSteppedPoly.SequenceX[nOuter].getArray(); 267 double* pNewY = aSteppedPoly.SequenceY[nOuter].getArray(); 268 double* pNewZ = aSteppedPoly.SequenceZ[nOuter].getArray(); 269 270 pNewX[0] = pOldX[0]; 271 pNewY[0] = pOldY[0]; 272 pNewZ[0] = pOldZ[0]; 273 for( sal_uInt32 oi = 0; oi < nMaxIndexPoints; oi++ ) 274 { 275 switch ( eCurveStyle ) 276 { 277 case CurveStyle_STEP_START: 278 /** O 279 | 280 | 281 | 282 O-----+ 283 */ 284 // create the intermediate point 285 pNewX[1+oi*2] = pOldX[oi+1]; 286 pNewY[1+oi*2] = pOldY[oi]; 287 pNewZ[1+oi*2] = pOldZ[oi]; 288 // and now the normal one 289 pNewX[1+oi*2+1] = pOldX[oi+1]; 290 pNewY[1+oi*2+1] = pOldY[oi+1]; 291 pNewZ[1+oi*2+1] = pOldZ[oi+1]; 292 break; 293 case CurveStyle_STEP_END: 294 /** +------O 295 | 296 | 297 | 298 O 299 */ 300 // create the intermediate point 301 pNewX[1+oi*2] = pOldX[oi]; 302 pNewY[1+oi*2] = pOldY[oi+1]; 303 pNewZ[1+oi*2] = pOldZ[oi]; 304 // and now the normal one 305 pNewX[1+oi*2+1] = pOldX[oi+1]; 306 pNewY[1+oi*2+1] = pOldY[oi+1]; 307 pNewZ[1+oi*2+1] = pOldZ[oi+1]; 308 break; 309 case CurveStyle_STEP_CENTER_X: 310 /** +--O 311 | 312 | 313 | 314 O--+ 315 */ 316 // create the first intermediate point 317 pNewX[1+oi*3] = (pOldX[oi]+pOldX[oi+1])/2; 318 pNewY[1+oi*3] = pOldY[oi]; 319 pNewZ[1+oi*3] = pOldZ[oi]; 320 // create the second intermediate point 321 pNewX[1+oi*3+1] = (pOldX[oi]+pOldX[oi+1])/2; 322 pNewY[1+oi*3+1] = pOldY[oi+1]; 323 pNewZ[1+oi*3+1] = pOldZ[oi]; 324 // and now the normal one 325 pNewX[1+oi*3+2] = pOldX[oi+1]; 326 pNewY[1+oi*3+2] = pOldY[oi+1]; 327 pNewZ[1+oi*3+2] = pOldZ[oi+1]; 328 break; 329 case CurveStyle_STEP_CENTER_Y: 330 /** O 331 | 332 +-----+ 333 | 334 O 335 */ 336 // create the first intermediate point 337 pNewX[1+oi*3] = pOldX[oi]; 338 pNewY[1+oi*3] = (pOldY[oi]+pOldY[oi+1])/2; 339 pNewZ[1+oi*3] = pOldZ[oi]; 340 // create the second intermediate point 341 pNewX[1+oi*3+1] = pOldX[oi+1]; 342 pNewY[1+oi*3+1] = (pOldY[oi]+pOldY[oi+1])/2; 343 pNewZ[1+oi*3+1] = pOldZ[oi]; 344 // and now the normal one 345 pNewX[1+oi*3+2] = pOldX[oi+1]; 346 pNewY[1+oi*3+2] = pOldY[oi+1]; 347 pNewZ[1+oi*3+2] = pOldZ[oi+1]; 348 break; 349 default: 350 // this should never be executed 351 OSL_FAIL("Unknown curvestyle in AreaChart::create_stepped_line"); 352 } 353 } 354 } 355 Clipping::clipPolygonAtRectangle( aSteppedPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly ); 356 357 return true; 358 } 359 360 bool AreaChart::impl_createLine( VDataSeries* pSeries 361 , drawing::PolyPolygonShape3D const * pSeriesPoly 362 , PlottingPositionHelper* pPosHelper ) 363 { 364 //return true if a line was created successfully 365 uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget); 366 367 drawing::PolyPolygonShape3D aPoly; 368 if(m_eCurveStyle==CurveStyle_CUBIC_SPLINES) 369 { 370 drawing::PolyPolygonShape3D aSplinePoly; 371 SplineCalculater::CalculateCubicSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution ); 372 lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper ); 373 Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly ); 374 } 375 else if(m_eCurveStyle==CurveStyle_B_SPLINES) 376 { 377 drawing::PolyPolygonShape3D aSplinePoly; 378 SplineCalculater::CalculateBSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution, m_nSplineOrder ); 379 lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper ); 380 Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly ); 381 } 382 else if (m_eCurveStyle==CurveStyle_STEP_START || 383 m_eCurveStyle==CurveStyle_STEP_END || 384 m_eCurveStyle==CurveStyle_STEP_CENTER_Y || 385 m_eCurveStyle==CurveStyle_STEP_CENTER_X 386 ) 387 { 388 if (!create_stepped_line(*pSeriesPoly, m_eCurveStyle, pPosHelper, aPoly)) 389 { 390 return false; 391 } 392 } 393 else 394 { // default to creating a straight line 395 SAL_WARN_IF(m_eCurveStyle != CurveStyle_LINES, "chart2.areachart", "Unknown curve style"); 396 Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly ); 397 } 398 399 if(!ShapeFactory::hasPolygonAnyLines(aPoly)) 400 return false; 401 402 //transformation 3) -> 4) 403 pPosHelper->transformScaledLogicToScene( aPoly ); 404 405 //create line: 406 uno::Reference< drawing::XShape > xShape; 407 if(m_nDimension==3) 408 { 409 double fDepth = getTransformedDepth(); 410 sal_Int32 nPolyCount = aPoly.SequenceX.getLength(); 411 for(sal_Int32 nPoly=0;nPoly<nPolyCount;nPoly++) 412 { 413 sal_Int32 nPointCount = aPoly.SequenceX[nPoly].getLength(); 414 for(sal_Int32 nPoint=0;nPoint<nPointCount-1;nPoint++) 415 { 416 drawing::Position3D aPoint1, aPoint2; 417 aPoint1.PositionX = aPoly.SequenceX[nPoly][nPoint+1]; 418 aPoint1.PositionY = aPoly.SequenceY[nPoly][nPoint+1]; 419 aPoint1.PositionZ = aPoly.SequenceZ[nPoly][nPoint+1]; 420 421 aPoint2.PositionX = aPoly.SequenceX[nPoly][nPoint]; 422 aPoint2.PositionY = aPoly.SequenceY[nPoly][nPoint]; 423 aPoint2.PositionZ = aPoly.SequenceZ[nPoly][nPoint]; 424 425 m_pShapeFactory->createStripe(xSeriesGroupShape_Shapes 426 , Stripe( aPoint1, aPoint2, fDepth ) 427 , pSeries->getPropertiesOfSeries(), PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), true, 1 ); 428 } 429 } 430 } 431 else //m_nDimension!=3 432 { 433 xShape = m_pShapeFactory->createLine2D( xSeriesGroupShape_Shapes 434 , PolyToPointSequence( aPoly ) ); 435 setMappedProperties( xShape 436 , pSeries->getPropertiesOfSeries() 437 , PropertyMapper::getPropertyNameMapForLineSeriesProperties() ); 438 //because of this name this line will be used for marking 439 ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles"); 440 } 441 return true; 442 } 443 444 bool AreaChart::impl_createArea( VDataSeries* pSeries 445 , drawing::PolyPolygonShape3D const * pSeriesPoly 446 , drawing::PolyPolygonShape3D const * pPreviousSeriesPoly 447 , PlottingPositionHelper const * pPosHelper ) 448 { 449 //return true if an area was created successfully 450 451 uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget); 452 double zValue = pSeries->m_fLogicZPos; 453 454 drawing::PolyPolygonShape3D aPoly( *pSeriesPoly ); 455 //add second part to the polygon (grounding points or previous series points) 456 if(!pPreviousSeriesPoly) 457 { 458 double fMinX = pSeries->m_fLogicMinX; 459 double fMaxX = pSeries->m_fLogicMaxX; 460 double fY = pPosHelper->getBaseValueY();//logic grounding 461 if( m_nDimension==3 ) 462 fY = pPosHelper->getLogicMinY(); 463 464 //clip to scale 465 if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX()) 466 return false;//no visible shape needed 467 pPosHelper->clipLogicValues( &fMinX, &fY, nullptr ); 468 pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr ); 469 470 //apply scaling 471 { 472 pPosHelper->doLogicScaling( &fMinX, &fY, &zValue ); 473 pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr ); 474 } 475 476 AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) ); 477 AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) ); 478 } 479 else 480 { 481 appendPoly( aPoly, *pPreviousSeriesPoly ); 482 } 483 ShapeFactory::closePolygon(aPoly); 484 485 //apply clipping 486 { 487 drawing::PolyPolygonShape3D aClippedPoly; 488 Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false ); 489 ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping 490 aPoly = aClippedPoly; 491 } 492 493 if(!ShapeFactory::hasPolygonAnyLines(aPoly)) 494 return false; 495 496 //transformation 3) -> 4) 497 pPosHelper->transformScaledLogicToScene( aPoly ); 498 499 //create area: 500 uno::Reference< drawing::XShape > xShape; 501 if(m_nDimension==3) 502 { 503 xShape = m_pShapeFactory->createArea3D( xSeriesGroupShape_Shapes 504 , aPoly, getTransformedDepth() ); 505 } 506 else //m_nDimension!=3 507 { 508 xShape = m_pShapeFactory->createArea2D( xSeriesGroupShape_Shapes 509 , aPoly ); 510 } 511 setMappedProperties( xShape 512 , pSeries->getPropertiesOfSeries() 513 , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() ); 514 //because of this name this line will be used for marking 515 ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles"); 516 return true; 517 } 518 519 void AreaChart::impl_createSeriesShapes() 520 { 521 //the polygon shapes for each series need to be created before 522 523 //iterate through all series again to create the series shapes 524 for( auto const& rZSlot : m_aZSlots ) 525 { 526 for( auto const& rXSlot : rZSlot ) 527 { 528 std::map< sal_Int32, drawing::PolyPolygonShape3D* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex 529 drawing::PolyPolygonShape3D* pSeriesPoly = nullptr; 530 531 //iterate through all series 532 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector ) 533 { 534 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex(); 535 PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex); 536 m_pPosHelper = &rPosHelper; 537 538 createRegressionCurvesShapes( *pSeries, m_xErrorBarTarget, m_xRegressionCurveEquationTarget, 539 m_pPosHelper->maySkipPointsInRegressionCalculation()); 540 541 pSeriesPoly = &pSeries->m_aPolyPolygonShape3D; 542 if( m_bArea ) 543 { 544 if (!impl_createArea(pSeries.get(), pSeriesPoly, 545 aPreviousSeriesPolyMap[nAttachedAxisIndex], &rPosHelper)) 546 continue; 547 } 548 if( m_bLine ) 549 { 550 if (!impl_createLine(pSeries.get(), pSeriesPoly, &rPosHelper)) 551 continue; 552 } 553 aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly; 554 }//next series in x slot (next y slot) 555 }//next x slot 556 }//next z slot 557 } 558 559 namespace 560 { 561 562 void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots ) 563 { 564 std::vector< std::vector< VDataSeriesGroup > > aRet; 565 aRet.reserve( rZSlots.size() ); 566 567 std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() ); 568 std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() ); 569 for( ; aZIt != aZEnd; ++aZIt ) 570 { 571 std::vector< VDataSeriesGroup > aXSlot; 572 aXSlot.reserve( aZIt->size() ); 573 574 std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() ); 575 std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() ); 576 for( ; aXIt != aXEnd; ++aXIt ) 577 aXSlot.push_back(std::move(*aXIt)); 578 579 aRet.push_back(std::move(aXSlot)); 580 } 581 582 rZSlots = std::move(aRet); 583 } 584 585 //better performance for big data 586 struct FormerPoint 587 { 588 FormerPoint( double fX, double fY, double fZ ) 589 : m_fX(fX), m_fY(fY), m_fZ(fZ) 590 {} 591 FormerPoint() 592 { 593 ::rtl::math::setNan( &m_fX ); 594 ::rtl::math::setNan( &m_fY ); 595 ::rtl::math::setNan( &m_fZ ); 596 } 597 598 double m_fX; 599 double m_fY; 600 double m_fZ; 601 }; 602 603 }//anonymous namespace 604 605 void AreaChart::createShapes() 606 { 607 if( m_aZSlots.empty() ) //no series 608 return; 609 610 //tdf#127813 Don't reverse the series in OOXML-heavy environments 611 if( officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_nDimension == 2 && ( m_bArea || !m_bCategoryXAxis ) ) 612 lcl_reorderSeries( m_aZSlots ); 613 614 OSL_ENSURE(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is(),"AreaChart is not proper initialized"); 615 if(!(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is())) 616 return; 617 618 //the text labels should be always on top of the other series shapes 619 //for area chart the error bars should be always on top of the other series shapes 620 621 //therefore create an own group for the texts and the error bars to move them to front 622 //(because the text group is created after the series group the texts are displayed on top) 623 m_xSeriesTarget = createGroupShape( m_xLogicTarget ); 624 if( m_bArea ) 625 m_xErrorBarTarget = createGroupShape( m_xLogicTarget ); 626 else 627 m_xErrorBarTarget = m_xSeriesTarget; 628 m_xTextTarget = m_pShapeFactory->createGroup2D( m_xFinalTarget ); 629 m_xRegressionCurveEquationTarget = m_pShapeFactory->createGroup2D( m_xFinalTarget ); 630 631 //check necessary here that different Y axis can not be stacked in the same group? ... hm? 632 633 //update/create information for current group 634 double fLogicZ = 1.0;//as defined 635 636 sal_Int32 nStartIndex = 0; // inclusive ;..todo get somehow from x scale 637 sal_Int32 nEndIndex = VSeriesPlotter::getPointCount(); 638 if(nEndIndex<=0) 639 nEndIndex=1; 640 641 //better performance for big data 642 std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap; 643 m_bPointsWereSkipped = false; 644 sal_Int32 nSkippedPoints = 0; 645 sal_Int32 nCreatedPoints = 0; 646 647 bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis()); 648 649 std::vector<std::map< sal_Int32, double > > aLogicYSumMapByX(nEndIndex);//one for each different nAttachedAxisIndex 650 for( auto const& rZSlot : m_aZSlots ) 651 { 652 //iterate through all x slots in this category to get 100percent sum 653 for( auto const& rXSlot : rZSlot ) 654 { 655 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector ) 656 { 657 if(!pSeries) 658 continue; 659 660 if (bDateCategory) 661 pSeries->doSortByXValues(); 662 663 for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ ) 664 { 665 std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex]; 666 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex(); 667 if( rLogicYSumMap.find(nAttachedAxisIndex)==rLogicYSumMap.end() ) 668 rLogicYSumMap[nAttachedAxisIndex]=0.0; 669 670 m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex); 671 672 double fAdd = pSeries->getYValue( nIndex ); 673 if( !std::isnan(fAdd) && !std::isinf(fAdd) ) 674 rLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd ); 675 } 676 } 677 } 678 } 679 680 sal_Int32 nZ=1; 681 for( auto const& rZSlot : m_aZSlots ) 682 { 683 //for the area chart there should be at most one x slot (no side by side stacking available) 684 //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not??? 685 for( auto const& rXSlot : rZSlot ) 686 { 687 std::vector<std::map< sal_Int32, double > > aLogicYForNextSeriesMapByX(nEndIndex); //one for each different nAttachedAxisIndex 688 //iterate through all series 689 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector ) 690 { 691 if(!pSeries) 692 continue; 693 694 uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget); 695 696 sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex(); 697 PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex); 698 m_pPosHelper = &rPosHelper; 699 700 if(m_nDimension==3) 701 fLogicZ = nZ+0.5; 702 pSeries->m_fLogicZPos = fLogicZ; 703 704 for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ ) 705 { 706 707 /* #i70133# ignore points outside of series length in standard area 708 charts. Stacked area charts will use missing points as zeros. In 709 standard charts, pSeriesList contains only one series. */ 710 if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) ) 711 continue; 712 713 //collect data point information (logic coordinates, style ): 714 double fLogicX = pSeries->getXValue(nIndex); 715 if (bDateCategory) 716 { 717 if (std::isnan(fLogicX)) 718 continue; 719 720 fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution ); 721 } 722 double fLogicY = pSeries->getYValue(nIndex); 723 724 if( m_nDimension==3 && m_bArea && rXSlot.m_aSeriesVector.size()!=1 ) 725 fLogicY = fabs( fLogicY ); 726 727 double fLogicValueForLabeDisplay = fLogicY; 728 std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex]; 729 if (rPosHelper.isPercentY() && rLogicYSumMap[nAttachedAxisIndex] != 0.0) 730 { 731 fLogicY = fabs( fLogicY )/rLogicYSumMap[nAttachedAxisIndex]; 732 } 733 734 if( std::isnan(fLogicX) || std::isinf(fLogicX) 735 || std::isnan(fLogicY) || std::isinf(fLogicY) 736 || std::isnan(fLogicZ) || std::isinf(fLogicZ) ) 737 { 738 if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP ) 739 { 740 drawing::PolyPolygonShape3D& rPolygon = pSeries->m_aPolyPolygonShape3D; 741 sal_Int32& rIndex = pSeries->m_nPolygonIndex; 742 if( 0<= rIndex && rIndex < rPolygon.SequenceX.getLength() ) 743 { 744 if( rPolygon.SequenceX[ rIndex ].hasElements() ) 745 rIndex++; //start a new polygon for the next point if the current poly is not empty 746 } 747 } 748 continue; 749 } 750 751 std::map< sal_Int32, double >& rLogicYForNextSeriesMap = aLogicYForNextSeriesMapByX[nIndex]; 752 rLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0); 753 754 double fPreviousYValue = rLogicYForNextSeriesMap[nAttachedAxisIndex]; 755 fLogicY += rLogicYForNextSeriesMap[nAttachedAxisIndex]; 756 rLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY; 757 758 bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ); 759 760 //remind minimal and maximal x values for area 'grounding' points 761 //only for filled area 762 { 763 double& rfMinX = pSeries->m_fLogicMinX; 764 if(!nIndex||fLogicX<rfMinX) 765 rfMinX=fLogicX; 766 double& rfMaxX = pSeries->m_fLogicMaxX; 767 if(!nIndex||fLogicX>rfMaxX) 768 rfMaxX=fLogicX; 769 } 770 771 drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ ); 772 drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition); 773 rPosHelper.doLogicScaling(aScaledLogicPosition); 774 775 //transformation 3) -> 4) 776 drawing::Position3D aScenePosition( 777 rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false)); 778 779 //better performance for big data 780 FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] ); 781 rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution); 782 if (!pSeries->isAttributedDataPoint(nIndex) 783 && rPosHelper.isSameForGivenResolution( 784 aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ, 785 aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, 786 aScaledLogicPosition.PositionZ)) 787 { 788 ++nSkippedPoints; 789 m_bPointsWereSkipped = true; 790 continue; 791 } 792 aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ); 793 794 //store point information for series polygon 795 //for area and/or line (symbols only do not need this) 796 if( isValidPosition(aScaledLogicPosition) ) 797 { 798 AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex ); 799 } 800 801 //create a single datapoint if point is visible 802 //apply clipping: 803 if( !bIsVisible ) 804 continue; 805 806 bool bCreateYErrorBar = false, bCreateXErrorBar = false; 807 { 808 uno::Reference< beans::XPropertySet > xErrorBarProp(pSeries->getYErrorBarProperties(nIndex)); 809 if( xErrorBarProp.is() ) 810 { 811 bool bShowPositive = false; 812 bool bShowNegative = false; 813 xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive; 814 xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative; 815 bCreateYErrorBar = bShowPositive || bShowNegative; 816 } 817 818 xErrorBarProp = pSeries->getXErrorBarProperties(nIndex); 819 if ( xErrorBarProp.is() ) 820 { 821 bool bShowPositive = false; 822 bool bShowNegative = false; 823 xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive; 824 xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative; 825 bCreateXErrorBar = bShowPositive || bShowNegative; 826 } 827 } 828 829 Symbol* pSymbolProperties = m_bSymbol ? pSeries->getSymbolProperties( nIndex ) : nullptr; 830 bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE); 831 832 if( !bCreateSymbol && !bCreateYErrorBar && 833 !bCreateXErrorBar && !pSeries->getDataPointLabelIfLabel(nIndex) ) 834 continue; 835 836 //create a group shape for this point and add to the series shape: 837 OUString aPointCID = ObjectIdentifier::createPointCID( 838 pSeries->getPointCID_Stub(), nIndex ); 839 uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( 840 createGroupShape(xSeriesGroupShape_Shapes,aPointCID) ); 841 uno::Reference<drawing::XShape> xPointGroupShape_Shape( xPointGroupShape_Shapes, uno::UNO_QUERY ); 842 843 { 844 nCreatedPoints++; 845 846 //create data point 847 drawing::Direction3D aSymbolSize(0,0,0); 848 if( bCreateSymbol ) 849 { 850 if(m_nDimension!=3) 851 { 852 if (pSymbolProperties->Style != SymbolStyle_NONE) 853 { 854 aSymbolSize.DirectionX = pSymbolProperties->Size.Width; 855 aSymbolSize.DirectionY = pSymbolProperties->Size.Height; 856 } 857 858 if (pSymbolProperties->Style == SymbolStyle_STANDARD) 859 { 860 sal_Int32 nSymbol = pSymbolProperties->StandardSymbol; 861 m_pShapeFactory->createSymbol2D( 862 xPointGroupShape_Shapes, aScenePosition, aSymbolSize, 863 nSymbol, pSymbolProperties->BorderColor, 864 pSymbolProperties->FillColor); 865 } 866 else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC) 867 { 868 m_pShapeFactory->createGraphic2D(xPointGroupShape_Shapes, 869 aScenePosition, aSymbolSize, 870 pSymbolProperties->Graphic); 871 } 872 //@todo other symbol styles 873 } 874 } 875 //create error bars or rectangles, depending on configuration 876 if ( ConfigAccess::getUseErrorRectangle() ) 877 { 878 if ( bCreateXErrorBar || bCreateYErrorBar ) 879 { 880 createErrorRectangle( 881 aUnscaledLogicPosition, 882 *pSeries, 883 nIndex, 884 m_xErrorBarTarget, 885 bCreateXErrorBar, 886 bCreateYErrorBar ); 887 } 888 } 889 else 890 { 891 if (bCreateXErrorBar) 892 createErrorBar_X( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget ); 893 894 if (bCreateYErrorBar) 895 createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget, nullptr ); 896 } 897 898 //create data point label 899 if( pSeries->getDataPointLabelIfLabel(nIndex) ) 900 { 901 LabelAlignment eAlignment = LABEL_ALIGN_TOP; 902 sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( 903 nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY()); 904 905 if (m_bArea && nLabelPlacement == css::chart::DataLabelPlacement::CENTER) 906 { 907 if (fPreviousYValue) 908 fLogicY -= (fLogicY - fPreviousYValue) / 2.0; 909 else 910 fLogicY = (fLogicY + rPosHelper.getLogicMinY()) / 2.0; 911 aScenePosition = rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false); 912 } 913 914 drawing::Position3D aScenePosition3D( aScenePosition.PositionX 915 , aScenePosition.PositionY 916 , aScenePosition.PositionZ+getTransformedDepth() ); 917 918 switch(nLabelPlacement) 919 { 920 case css::chart::DataLabelPlacement::TOP: 921 aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1); 922 eAlignment = LABEL_ALIGN_TOP; 923 break; 924 case css::chart::DataLabelPlacement::BOTTOM: 925 aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1); 926 eAlignment = LABEL_ALIGN_BOTTOM; 927 break; 928 case css::chart::DataLabelPlacement::LEFT: 929 aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1); 930 eAlignment = LABEL_ALIGN_LEFT; 931 break; 932 case css::chart::DataLabelPlacement::RIGHT: 933 aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1); 934 eAlignment = LABEL_ALIGN_RIGHT; 935 break; 936 case css::chart::DataLabelPlacement::CENTER: 937 eAlignment = LABEL_ALIGN_CENTER; 938 break; 939 default: 940 OSL_FAIL("this label alignment is not implemented yet"); 941 aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1); 942 eAlignment = LABEL_ALIGN_TOP; 943 break; 944 } 945 946 awt::Point aScreenPosition2D;//get the screen position for the labels 947 sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent 948 { 949 if(eAlignment==LABEL_ALIGN_CENTER || m_nDimension == 3 ) 950 nOffset = 0; 951 aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget,m_pShapeFactory) 952 .transformSceneToScreenPosition( aScenePosition3D ); 953 } 954 955 createDataLabel( m_xTextTarget, *pSeries, nIndex 956 , fLogicValueForLabeDisplay 957 , rLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset ); 958 } 959 } 960 961 //remove PointGroupShape if empty 962 if(!xPointGroupShape_Shapes->getCount()) 963 xSeriesGroupShape_Shapes->remove(xPointGroupShape_Shape); 964 } 965 966 }//next series in x slot (next y slot) 967 }//next x slot 968 ++nZ; 969 }//next z slot 970 971 impl_createSeriesShapes(); 972 973 /* @todo remove series shapes if empty 974 //remove and delete point-group-shape if empty 975 if(!xSeriesGroupShape_Shapes->getCount()) 976 { 977 pSeries->m_xShape.set(NULL); 978 m_xLogicTarget->remove(xSeriesGroupShape_Shape); 979 } 980 */ 981 982 //remove and delete series-group-shape if empty 983 984 //... todo 985 986 SAL_INFO( 987 "chart2", 988 "skipped points: " << nSkippedPoints << " created points: " 989 << nCreatedPoints); 990 } 991 992 } //namespace chart 993 994 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 995
