LibreOffice Module chart2 (master) 1
BarChart.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 "BarChart.hxx"
21#include "BarPositionHelper.hxx"
22
23#include <ChartType.hxx>
24#include <ShapeFactory.hxx>
25#include <CommonConverters.hxx>
26#include <ObjectIdentifier.hxx>
28#include <AxisIndexDefines.hxx>
29#include <Clipping.hxx>
30#include <DateHelper.hxx>
31#include <svx/scene3d.hxx>
33
34#include <com/sun/star/chart/DataLabelPlacement.hpp>
35
36#include <com/sun/star/chart2/DataPointGeometry3D.hpp>
37#include <rtl/math.hxx>
39
40namespace chart
41{
42using namespace ::com::sun::star;
43using namespace ::rtl::math;
44using namespace ::com::sun::star::chart2;
45
47 , sal_Int32 nDimensionCount )
48 : VSeriesPlotter( xChartTypeModel, nDimensionCount )
49 , m_pMainPosHelper( new BarPositionHelper() )
50{
51 PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
52 VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
53
54 try
55 {
56 if( m_xChartTypeModel.is() )
57 {
58 m_xChartTypeModel->getPropertyValue( "OverlapSequence" ) >>= m_aOverlapSequence;
59 m_xChartTypeModel->getPropertyValue( "GapwidthSequence" ) >>= m_aGapwidthSequence;
60 }
61 }
62 catch( const uno::Exception& )
63 {
64 TOOLS_WARN_EXCEPTION("chart2", "" );
65 }
66}
67
68BarChart::~BarChart()
69{
70}
71
72PlottingPositionHelper& BarChart::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
73{
74 PlottingPositionHelper& rPosHelper = VSeriesPlotter::getPlottingPositionHelper( nAxisIndex );
75 BarPositionHelper* pBarPosHelper = dynamic_cast<BarPositionHelper*>(&rPosHelper);
76 if( pBarPosHelper && nAxisIndex >= 0 )
77 {
78 if( nAxisIndex < m_aOverlapSequence.getLength() )
79 pBarPosHelper->setInnerDistance( -m_aOverlapSequence[nAxisIndex]/100.0 );
80 if( nAxisIndex < m_aGapwidthSequence.getLength() )
81 pBarPosHelper->setOuterDistance( m_aGapwidthSequence[nAxisIndex]/100.0 );
82 }
83 return rPosHelper;
84}
85
86drawing::Direction3D BarChart::getPreferredDiagramAspectRatio() const
87{
88 drawing::Direction3D aRet(1.0,1.0,1.0);
89 if( m_nDimension == 3 )
90 {
91 aRet = drawing::Direction3D(1.0,-1.0,1.0);
92 BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( MAIN_AXIS_INDEX) ) );
93 if (pPosHelper)
94 {
95 drawing::Direction3D aScale( pPosHelper->getScaledLogicWidth() );
96 if(aScale.DirectionX!=0.0)
97 {
98 double fXSlotCount = 1.0;
99 if(!m_aZSlots.empty())
100 {
101 fXSlotCount = m_aZSlots.begin()->size();
102 }
103 aRet.DirectionZ = aScale.DirectionZ /
104 (aScale.DirectionX + aScale.DirectionX * (fXSlotCount-1.0) * pPosHelper->getScaledSlotWidth());
105 }
106 else
107 {
108 return VSeriesPlotter::getPreferredDiagramAspectRatio();
109 }
110 }
111 else
112 {
113 return VSeriesPlotter::getPreferredDiagramAspectRatio();
114 }
115
116 if(aRet.DirectionZ<0.05)
117 {
118 aRet.DirectionZ=0.05;
119 }
120 else if(aRet.DirectionZ>10)
121 {
122 aRet.DirectionZ=10;
123 }
124 if( m_pMainPosHelper && m_pMainPosHelper->isSwapXAndY() )
125 {
126 std::swap(aRet.DirectionX, aRet.DirectionY);
127 }
128 }
129 else
130 aRet = drawing::Direction3D(-1,-1,-1);
131 return aRet;
132}
133
134awt::Point BarChart::getLabelScreenPositionAndAlignment(
135 LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
136 , double fScaledX, double fScaledLowerYValue, double fScaledUpperYValue, double fScaledZ
137 , double fScaledLowerBarDepth, double fScaledUpperBarDepth, double fBaseValue
138 , BarPositionHelper const * pPosHelper
139 ) const
140{
141 double fX = fScaledX;
142 double fY = fScaledUpperYValue;
143 double fZ = fScaledZ;
144 bool bReverse = !pPosHelper->isMathematicalOrientationY();
145 bool bNormalOutside = (!bReverse == (fBaseValue < fScaledUpperYValue));
146 double fDepth = fScaledUpperBarDepth;
147
148 switch(nLabelPlacement)
149 {
150 case css::chart::DataLabelPlacement::TOP:
151 {
152 if( !pPosHelper->isSwapXAndY() )
153 {
154 fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
155 rAlignment = LABEL_ALIGN_TOP;
156 if(m_nDimension==3)
157 fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
158 }
159 else
160 {
161 fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
162 rAlignment = LABEL_ALIGN_CENTER;
163 OSL_FAIL( "top label placement is not really supported by horizontal bar charts" );
164 }
165 }
166 break;
167 case css::chart::DataLabelPlacement::BOTTOM:
168 {
169 if(!pPosHelper->isSwapXAndY())
170 {
171 fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
172 rAlignment = LABEL_ALIGN_BOTTOM;
173 if(m_nDimension==3)
174 fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
175 }
176 else
177 {
178 fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
179 rAlignment = LABEL_ALIGN_CENTER;
180 OSL_FAIL( "bottom label placement is not supported by horizontal bar charts" );
181 }
182 }
183 break;
184 case css::chart::DataLabelPlacement::LEFT:
185 {
186 if( pPosHelper->isSwapXAndY() )
187 {
188 fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
189 rAlignment = LABEL_ALIGN_LEFT;
190 if(m_nDimension==3)
191 fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
192 }
193 else
194 {
195 fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
196 rAlignment = LABEL_ALIGN_CENTER;
197 OSL_FAIL( "left label placement is not supported by column charts" );
198 }
199 }
200 break;
201 case css::chart::DataLabelPlacement::RIGHT:
202 {
203 if( pPosHelper->isSwapXAndY() )
204 {
205 fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
206 rAlignment = LABEL_ALIGN_RIGHT;
207 if(m_nDimension==3)
208 fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
209 }
210 else
211 {
212 fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
213 rAlignment = LABEL_ALIGN_CENTER;
214 OSL_FAIL( "right label placement is not supported by column charts" );
215 }
216 }
217 break;
218 case css::chart::DataLabelPlacement::OUTSIDE:
219 {
220 fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
221 if( pPosHelper->isSwapXAndY() )
222 // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
223 if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
224 rAlignment = LABEL_ALIGN_RIGHT;
225 else
226 rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
227 else
228 // if datapoint value is 0 the label will appear TOP in case of Column Chart
229 if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
230 rAlignment = LABEL_ALIGN_TOP;
231 else
232 rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
233 if(m_nDimension==3)
234 fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
235 }
236 break;
237 case css::chart::DataLabelPlacement::INSIDE:
238 {
239 fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
240 if( pPosHelper->isSwapXAndY() )
241 rAlignment = bNormalOutside ? LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
242 else
243 rAlignment = bNormalOutside ? LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
244 if(m_nDimension==3)
245 fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
246 }
247 break;
248 case css::chart::DataLabelPlacement::NEAR_ORIGIN:
249 {
250 fY = (fBaseValue < fScaledUpperYValue) ? fScaledLowerYValue : fScaledUpperYValue;
251 if( pPosHelper->isSwapXAndY() )
252 // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
253 if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
254 rAlignment = LABEL_ALIGN_RIGHT;
255 else
256 rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
257 else
258 // if datapoint value is 0 the label will appear TOP in case of Column Chart
259 if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
260 rAlignment = LABEL_ALIGN_TOP;
261 else
262 rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
263 if(m_nDimension==3)
264 fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
265 }
266 break;
267 case css::chart::DataLabelPlacement::CENTER:
268 fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
269 // if datapoint value is 0 the label will appear TOP/RIGHT in case of Column/Bar Charts
270 if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
271 if( pPosHelper->isSwapXAndY() )
272 rAlignment = LABEL_ALIGN_RIGHT;
273 else
274 rAlignment = LABEL_ALIGN_TOP;
275 else
276 rAlignment = LABEL_ALIGN_CENTER;
277 if(m_nDimension==3)
278 fDepth = fabs(fScaledUpperBarDepth-fScaledLowerBarDepth)/2.0;
279 break;
280 default:
281 OSL_FAIL("this label alignment is not implemented yet");
282
283 break;
284 }
285 if(m_nDimension==3)
286 fZ -= fDepth/2.0;
287
288 drawing::Position3D aScenePosition3D( pPosHelper->
289 transformScaledLogicToScene( fX, fY, fZ, true ) );
290 return LabelPositionHelper(m_nDimension,m_xLogicTarget)
291 .transformSceneToScreenPosition( aScenePosition3D );
292}
293
294rtl::Reference< SvxShape > BarChart::createDataPoint3D_Bar(
296 , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
297 , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
298 , const uno::Reference< beans::XPropertySet >& xObjectProperties
299 , sal_Int32 nGeometry3D )
300{
301 bool bRoundedEdges = true;
302 try
303 {
304 if( xObjectProperties.is() )
305 {
306 sal_Int16 nPercentDiagonal = 0;
307 xObjectProperties->getPropertyValue( "PercentDiagonal" ) >>= nPercentDiagonal;
308 if( nPercentDiagonal < 5 )
309 bRoundedEdges = false;
310 }
311 }
312 catch( const uno::Exception& )
313 {
314 TOOLS_WARN_EXCEPTION("chart2", "" );
315 }
316
318 switch( nGeometry3D )
319 {
320 case DataPointGeometry3D::CYLINDER:
321 xShape = ShapeFactory::createCylinder( xTarget, rPosition, rSize, nRotateZAngleHundredthDegree );
322 break;
323 case DataPointGeometry3D::CONE:
324 xShape = ShapeFactory::createCone( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree );
325 break;
326 case DataPointGeometry3D::PYRAMID:
327 xShape = ShapeFactory::createPyramid( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree>0
328 , xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
329 break;
330 case DataPointGeometry3D::CUBOID:
331 default:
332 xShape = ShapeFactory::createCube( xTarget, rPosition, rSize
333 , nRotateZAngleHundredthDegree, xObjectProperties
334 , PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), bRoundedEdges );
335 return xShape;
336 }
337 if( nGeometry3D != DataPointGeometry3D::PYRAMID )
338 PropertyMapper::setMappedProperties( *xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
339 return xShape;
340}
341
342namespace
343{
344bool lcl_hasGeometry3DVariableWidth( sal_Int32 nGeometry3D )
345{
346 bool bRet = false;
347 switch( nGeometry3D )
348 {
349 case DataPointGeometry3D::PYRAMID:
350 case DataPointGeometry3D::CONE:
351 bRet = true;
352 break;
353 case DataPointGeometry3D::CUBOID:
354 case DataPointGeometry3D::CYLINDER:
355 default:
356 bRet = false;
357 break;
358 }
359 return bRet;
360}
361}// end anonymous namespace
362
363void BarChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
364{
365 if( !pSeries )
366 return;
367 if(m_nDimension==2)
368 {
369 //2ND_AXIS_IN_BARS put series on second scales to different z slot as temporary workaround
370 //this needs to be redesigned if 3d bars are also able to display secondary axes
371
372 sal_Int32 nAxisIndex = pSeries->getAttachedAxisIndex();
373 zSlot = nAxisIndex;
374
375 if( !pSeries->getGroupBarsPerAxis() )
376 zSlot = 0;
377 if(zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
378 m_aZSlots.resize(zSlot+1);
379 }
380 VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
381}
382
383void BarChart::adaptOverlapAndGapwidthForGroupBarsPerAxis()
384{
385 //adapt m_aOverlapSequence and m_aGapwidthSequence for the groupBarsPerAxis feature
386 //thus the different series use the same settings
387
388 VDataSeries* pFirstSeries = getFirstSeries();
389 if(!pFirstSeries || pFirstSeries->getGroupBarsPerAxis())
390 return;
391
392 sal_Int32 nAxisIndex = pFirstSeries->getAttachedAxisIndex();
393 sal_Int32 nN = 0;
394 sal_Int32 nUseThisIndex = nAxisIndex;
395 if( nUseThisIndex < 0 || nUseThisIndex >= m_aOverlapSequence.getLength() )
396 nUseThisIndex = 0;
397 auto aOverlapSequenceRange = asNonConstRange(m_aOverlapSequence);
398 for( nN = 0; nN < m_aOverlapSequence.getLength(); nN++ )
399 {
400 if(nN!=nUseThisIndex)
401 aOverlapSequenceRange[nN] = m_aOverlapSequence[nUseThisIndex];
402 }
403
404 nUseThisIndex = nAxisIndex;
405 if( nUseThisIndex < 0 || nUseThisIndex >= m_aGapwidthSequence.getLength() )
406 nUseThisIndex = 0;
407 auto aGapwidthSequenceRange = asNonConstRange(m_aGapwidthSequence);
408 for( nN = 0; nN < m_aGapwidthSequence.getLength(); nN++ )
409 {
410 if(nN!=nUseThisIndex)
411 aGapwidthSequenceRange[nN] = m_aGapwidthSequence[nUseThisIndex];
412 }
413}
414
415void BarChart::createShapes()
416{
417 if( m_aZSlots.empty() ) //no series
418 return;
419
420 OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"BarChart is not proper initialized");
421 if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
422 return;
423
424 //the text labels should be always on top of the other series shapes
425 //therefore create an own group for the texts to move them to front
426 //(because the text group is created after the series group the texts are displayed on top)
427
428 //the regression curves should always be on top of the bars but beneath the text labels
429 //to achieve this the regression curve target is created after the series target and before the text target
430
431 rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
432 rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveTarget = createGroupShape( m_xLogicTarget );
433 rtl::Reference<SvxShapeGroupAnyD> xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
434
435 rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveEquationTarget =
436 ShapeFactory::createGroup2D( m_xFinalTarget );
437 //check necessary here that different Y axis can not be stacked in the same group? ... hm?
438
439 bool bDrawConnectionLines = false;
440 bool bDrawConnectionLinesInited = false;
441
442 std::unordered_set<rtl::Reference<SvxShape>> aShapeSet;
443
444 const comphelper::ScopeGuard aGuard([aShapeSet]() {
445
446 std::unordered_set<E3dScene*> aSceneSet;
447
448 for (rtl::Reference<SvxShape> const & rShape : aShapeSet)
449 {
450 if(E3dScene* pScene = DynCastE3dScene(rShape->GetSdrObject()))
451 {
452 aSceneSet.insert(pScene->getRootE3dSceneFromE3dObject());
453 }
454 }
455 for (E3dScene* pScene : aSceneSet)
456 {
457 pScene->ResumeReportingDirtyRects();
458 pScene->SetAllSceneRectsDirty();
459 }
460 });
461
462 adaptOverlapAndGapwidthForGroupBarsPerAxis();
463
464 //better performance for big data
465 std::map< VDataSeries*, FormerBarPoint > aSeriesFormerPointMap;
466 m_bPointsWereSkipped = false;
467
468 sal_Int32 nStartIndex = 0;
469 sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
470 //iterate through all x values per indices
471 for( sal_Int32 nPointIndex = nStartIndex; nPointIndex < nEndIndex; nPointIndex++ )
472 {
473 //sum up the values for all series in a complete z slot per attached axis
474 std::map< sal_Int32, double > aLogicYSumMap;
475 for( auto& rZSlot : m_aZSlots )
476 {
477 for( auto& rXSlot : rZSlot )
478 {
479 sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
480 aLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
481
482 const sal_Int32 nSlotPoints = rXSlot.getPointCount();
483 if( nPointIndex >= nSlotPoints )
484 continue;
485
486 double fMinimumY = 0.0, fMaximumY = 0.0;
487 rXSlot.calculateYMinAndMaxForCategory( nPointIndex
488 , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
489
490 if( !std::isnan( fMaximumY ) && fMaximumY > 0)
491 aLogicYSumMap[nAttachedAxisIndex] += fMaximumY;
492 if( !std::isnan( fMinimumY ) && fMinimumY < 0)
493 aLogicYSumMap[nAttachedAxisIndex] += fabs(fMinimumY);
494 }
495 }
496
497 sal_Int32 nZ=1;
498 for( auto& rZSlot : m_aZSlots )
499 {
500 doZSlot(bDrawConnectionLines, bDrawConnectionLinesInited, rZSlot, nZ, nPointIndex, nStartIndex,
501 xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
502 aShapeSet, aSeriesFormerPointMap, aLogicYSumMap);
503 ++nZ;
504 }//next z slot
505 }//next category
506 if( bDrawConnectionLines )
507 {
508 for( auto const& rZSlot : m_aZSlots )
509 {
510 BarPositionHelper* pPosHelper = m_pMainPosHelper.get();
511 if( !rZSlot.empty() )
512 {
513 sal_Int32 nAttachedAxisIndex = rZSlot.front().getAttachedAxisIndexForFirstSeries();
514 //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
515 pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
516 if(!pPosHelper)
517 pPosHelper = m_pMainPosHelper.get();
518 }
519 PlotterBase::m_pPosHelper = pPosHelper;
520
521 //iterate through all x slots in this category
522 for( auto const& rXSlot : rZSlot )
523 {
524 //iterate through all series in this x slot
525 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
526 {
527 if(!pSeries)
528 continue;
529 std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
530 if(!ShapeFactory::hasPolygonAnyLines(*pSeriesPoly))
531 continue;
532
533 std::vector<std::vector<css::drawing::Position3D>> aPoly;
534 Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
535
536 if(!ShapeFactory::hasPolygonAnyLines(aPoly))
537 continue;
538
539 //transformation 3) -> 4)
540 pPosHelper->transformScaledLogicToScene( aPoly );
541
542 rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(
543 getSeriesGroupShape(pSeries.get(), xSeriesTarget) );
544 rtl::Reference<SvxShapePolyPolygon> xShape( ShapeFactory::createLine2D(
545 xSeriesGroupShape_Shapes, aPoly ) );
546 PropertyMapper::setMappedProperties( *xShape, pSeries->getPropertiesOfSeries()
547 , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
548 }
549 }
550 }
551 }
552
553 /* @todo remove series shapes if empty
554 */
555}
556
557void BarChart::doZSlot(
558 bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
559 const std::vector< VDataSeriesGroup >& rZSlot,
560 const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
561 const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
562 const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
563 const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
564 const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
565 std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
566 std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
567 std::map< sal_Int32, double >& aLogicYSumMap)
568{
569 //iterate through all x slots in this category
570 double fSlotX=0;
571 for( auto& rXSlot : rZSlot )
572 {
573 sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
574 //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
575 BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
576 if(!pPosHelper)
577 pPosHelper = m_pMainPosHelper.get();
578
579 PlotterBase::m_pPosHelper = pPosHelper;
580
581 //update/create information for current group
582 pPosHelper->updateSeriesCount( rZSlot.size() );
583 double fLogicBaseWidth = pPosHelper->getScaledSlotWidth();
584
585 // get distance from base value to maximum and minimum
586
587 double fMinimumY = 0.0, fMaximumY = 0.0;
588 if( nPointIndex < rXSlot.getPointCount())
589 rXSlot.calculateYMinAndMaxForCategory( nPointIndex
590 , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
591
592 double fLogicPositiveYSum = 0.0;
593 if( !std::isnan( fMaximumY ) )
594 fLogicPositiveYSum = fMaximumY;
595
596 double fLogicNegativeYSum = 0.0;
597 if( !std::isnan( fMinimumY ) )
598 fLogicNegativeYSum = fMinimumY;
599
600 if( pPosHelper->isPercentY() )
601 {
602 /* #i70395# fLogicPositiveYSum contains sum of all positive
603 values, if any, otherwise the highest negative value.
604 fLogicNegativeYSum contains sum of all negative values,
605 if any, otherwise the lowest positive value.
606 Afterwards, fLogicPositiveYSum will contain the maximum
607 (positive) value that is related to 100%. */
608
609 // do nothing if there are positive values only
610 if( fLogicNegativeYSum < 0.0 )
611 {
612 // fLogicPositiveYSum<0 => negative values only, use absolute of negative sum
613 if( fLogicPositiveYSum < 0.0 )
614 fLogicPositiveYSum = -fLogicNegativeYSum;
615 // otherwise there are positive and negative values, calculate total distance
616 else
617 fLogicPositiveYSum -= fLogicNegativeYSum;
618 }
619 fLogicNegativeYSum = 0.0;
620 }
621
622 doXSlot(rXSlot, bDrawConnectionLines, bDrawConnectionLinesInited, nZ, nPointIndex, nStartIndex,
623 xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
624 aShapeSet, aSeriesFormerPointMap, aLogicYSumMap,
625 fLogicBaseWidth, fSlotX, pPosHelper, fLogicPositiveYSum, fLogicNegativeYSum, nAttachedAxisIndex);
626
627 fSlotX+=1.0;
628 }//next x slot
629}
630
631
632void BarChart::doXSlot(
633 const VDataSeriesGroup& rXSlot,
634 bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
635 const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
636 const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
637 const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
638 const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
639 const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
640 std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
641 std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
642 std::map< sal_Int32, double >& aLogicYSumMap,
643 const double fLogicBaseWidth, const double fSlotX,
644 BarPositionHelper* const pPosHelper,
645 const double fLogicPositiveYSum, const double fLogicNegativeYSum,
646 const sal_Int32 nAttachedAxisIndex)
647{
648 double fBaseValue = 0.0;
649 if( !pPosHelper->isPercentY() && rXSlot.m_aSeriesVector.size()<=1 )
650 fBaseValue = pPosHelper->getBaseValueY();
651 double fPositiveLogicYForNextSeries = fBaseValue;
652 double fNegativeLogicYForNextSeries = fBaseValue;
653
654 //iterate through all series in this x slot
655 for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
656 {
657 if(!pSeries)
658 continue;
659
660 bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
661
662 bool bOnlyConnectionLinesForThisPoint = false;
663
664 if(nPointIndex==nStartIndex)//do not create a regression line for each point
665 createRegressionCurvesShapes( *pSeries, xRegressionCurveTarget, xRegressionCurveEquationTarget,
666 m_pPosHelper->maySkipPointsInRegressionCalculation());
667
668 if( !bDrawConnectionLinesInited )
669 {
670 bDrawConnectionLines = pSeries->getConnectBars();
671 if( m_nDimension==3 )
672 bDrawConnectionLines = false;
673 if( bDrawConnectionLines && rXSlot.m_aSeriesVector.size()==1 )
674 {
675 //detect whether we have a stacked chart or not:
676 StackingDirection eDirection = pSeries->getStackingDirection();
677 if( eDirection != StackingDirection_Y_STACKING )
678 bDrawConnectionLines = false;
679 }
680 bDrawConnectionLinesInited = true;
681 }
682
683 // Use another XShapes for background, so we can avoid needing to set the Z-order on all of them,
684 // which is expensive in bulk.
685 rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
686 rtl::Reference<SvxShapeGroupAnyD> xSeriesBackgroundShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
687 aShapeSet.insert(xSeriesGroupShape_Shapes);
688 aShapeSet.insert(xSeriesBackgroundShape_Shapes);
689 // Suspend setting rects dirty for the duration of this call
690 E3dScene* pScene = DynCastE3dScene(xSeriesGroupShape_Shapes->GetSdrObject());
691 if (pScene)
693 pScene = DynCastE3dScene(xSeriesBackgroundShape_Shapes->GetSdrObject());
694 if (pScene)
696
697 //collect data point information (logic coordinates, style ):
698 double fUnscaledLogicX = pSeries->getXValue( nPointIndex );
699 fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution );
700 if(std::isnan(fUnscaledLogicX))
701 continue;//point not visible
702 if(fUnscaledLogicX<pPosHelper->getLogicMinX())
703 continue;//point not visible
704 if(fUnscaledLogicX>pPosHelper->getLogicMaxX())
705 continue;//point not visible
706 if(pPosHelper->isStrongLowerRequested(0) && fUnscaledLogicX==pPosHelper->getLogicMaxX())
707 continue;//point not visible
708 double fLogicX = pPosHelper->getScaledSlotPos( fUnscaledLogicX, fSlotX );
709
710 double fLogicBarHeight = pSeries->getYValue( nPointIndex );
711 if( std::isnan( fLogicBarHeight )) //no value at this category
712 continue;
713
714 double fLogicValueForLabeDisplay = fLogicBarHeight;
715 fLogicBarHeight-=fBaseValue;
716
717 if( pPosHelper->isPercentY() )
718 {
719 if(fLogicPositiveYSum!=0.0)
720 fLogicBarHeight = fabs( fLogicBarHeight )/fLogicPositiveYSum;
721 else
722 fLogicBarHeight = 0.0;
723 }
724
725 // tdf#114141 to draw the top of the zero height 3D bar
726 // we set a small positive value, here the smallest one for the type double (DBL_MIN)
727 if( fLogicBarHeight == 0.0 )
728 fLogicBarHeight = DBL_MIN;
729
730 //sort negative and positive values, to display them on different sides of the x axis
731 bool bPositive = fLogicBarHeight >= 0.0;
732 double fLowerYValue = bPositive ? fPositiveLogicYForNextSeries : fNegativeLogicYForNextSeries;
733 double fUpperYValue = fLowerYValue+fLogicBarHeight;
734 if( bPositive )
735 fPositiveLogicYForNextSeries += fLogicBarHeight;
736 else
737 fNegativeLogicYForNextSeries += fLogicBarHeight;
738
739 double fLogicZ = 1.0;//as defined
740 if(m_nDimension==3)
741 fLogicZ = nZ+0.5;
742
743 drawing::Position3D aUnscaledLogicPosition( fUnscaledLogicX, fUpperYValue, fLogicZ );
744
745 //@todo ... start an iteration over the different breaks of the axis
746 //each subsystem may add an additional shape to form the whole point
747 //create a group shape for this point and add to the series shape:
748// uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( createGroupShape(xSeriesGroupShape_Shapes) );
749// uno::Reference<drawing::XShape> xPointGroupShape_Shape =
750// uno::Reference<drawing::XShape>( xPointGroupShape_Shapes, uno::UNO_QUERY );
751 //as long as we do not iterate we do not need to create an additional group for each point
752 uno::Reference< beans::XPropertySet > xDataPointProperties( pSeries->getPropertiesOfPoint( nPointIndex ) );
753 sal_Int32 nGeometry3D = DataPointGeometry3D::CUBOID;
754 if(m_nDimension==3) try
755 {
756 xDataPointProperties->getPropertyValue( "Geometry3D") >>= nGeometry3D;
757 }
758 catch( const uno::Exception& )
759 {
760 TOOLS_WARN_EXCEPTION("chart2", "" );
761 }
762
763 //@todo iterate through all subsystems to create partial points
764 {
765 //@todo select a suitable PositionHelper for this subsystem
766 BarPositionHelper* pSubPosHelper = pPosHelper;
767
768 double fUnclippedUpperYValue = fUpperYValue;
769
770 //apply clipping to Y
771 if( !pPosHelper->clipYRange(fLowerYValue,fUpperYValue) )
772 {
773 if( bDrawConnectionLines )
774 bOnlyConnectionLinesForThisPoint = true;
775 else
776 continue;
777 }
778 //@todo clipping of X and Z is not fully integrated so far, as there is a need to create different objects
779
780 //apply scaling to Y before calculating width (necessary to maintain gradient in clipped objects)
781 pSubPosHelper->doLogicScaling(nullptr,&fLowerYValue,nullptr);
782 pSubPosHelper->doLogicScaling(nullptr,&fUpperYValue,nullptr);
783 //scaling of X and Z is not provided as the created objects should be symmetric in that dimensions
784
785 pSubPosHelper->doLogicScaling(nullptr,&fUnclippedUpperYValue,nullptr);
786
787 //calculate resulting width
788 double fCompleteHeight = bPositive ? fLogicPositiveYSum : fLogicNegativeYSum;
789 if( pPosHelper->isPercentY() )
790 fCompleteHeight = 1.0;
791 double fLogicBarWidth = fLogicBaseWidth;
792 double fTopHeight=approxSub(fCompleteHeight,fUpperYValue);
793 if(!bPositive)
794 fTopHeight=approxSub(fCompleteHeight,fLowerYValue);
795 double fLogicYStart = bPositive ? fLowerYValue : fUpperYValue;
796 double fMiddleHeight = fUpperYValue-fLowerYValue;
797 if(!bPositive)
798 fMiddleHeight*=-1.0;
799 double fLogicBarDepth = 0.5;
800 if(m_nDimension==3)
801 {
802 if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
803 {
804 double fHeight = fCompleteHeight-fLowerYValue;
805 if(!bPositive)
806 fHeight = fCompleteHeight-fUpperYValue;
807 fLogicBarWidth = fLogicBaseWidth*fHeight/fCompleteHeight;
808 if(fLogicBarWidth<=0.0)
809 fLogicBarWidth=fLogicBaseWidth;
810 fLogicBarDepth = fLogicBarDepth*fHeight/fCompleteHeight;
811 if(fLogicBarDepth<=0.0)
812 fLogicBarDepth*=-1.0;
813 }
814 }
815
816 //better performance for big data
817 FormerBarPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
818 pPosHelper->setCoordinateSystemResolution( m_aCoordinateSystemResolution );
819 if( !pSeries->isAttributedDataPoint(nPointIndex)
820 &&
821 pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fUpperY, aFormerPoint.m_fZ
822 , fLogicX, fUpperYValue, fLogicZ )
823 &&
824 pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fLowerY, aFormerPoint.m_fZ
825 , fLogicX, fLowerYValue, fLogicZ )
826 )
827 {
828 m_bPointsWereSkipped = true;
829 continue;
830 }
831 aSeriesFormerPointMap[pSeries.get()] = FormerBarPoint(fLogicX,fUpperYValue,fLowerYValue,fLogicZ);
832
833 if( bDrawConnectionLines )
834 {
835 //store point information for connection lines
836
837 drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
838 drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
839
840 if( isValidPosition(aLeftUpperPoint) )
841 AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aLeftUpperPoint );
842 if( isValidPosition(aRightUpperPoint) )
843 AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aRightUpperPoint );
844 }
845
846 if( bOnlyConnectionLinesForThisPoint )
847 continue;
848
849 //maybe additional possibility for performance improvement
850 //bool bCreateLineInsteadOfComplexGeometryDueToMissingSpace = false;
851 //pPosHelper->isSameForGivenResolution( fLogicX-fLogicBarWidth/2.0, fLowerYValue, fLogicZ
852 // , fLogicX+fLogicBarWidth/2.0, fLowerYValue, fLogicZ );
853
854 //create partial point
855 if( !approxEqual(fLowerYValue,fUpperYValue) )
856 {
858 if( m_nDimension==3 )
859 {
860 drawing::Position3D aLogicBottom (fLogicX,fLogicYStart,fLogicZ);
861 drawing::Position3D aLogicLeftBottomFront (fLogicX+fLogicBarWidth/2.0,fLogicYStart,fLogicZ-fLogicBarDepth/2.0);
862 drawing::Position3D aLogicRightDeepTop (fLogicX-fLogicBarWidth/2.0,fLogicYStart+fMiddleHeight,fLogicZ+fLogicBarDepth/2.0);
863 drawing::Position3D aLogicTopTop (fLogicX,fLogicYStart+fMiddleHeight+fTopHeight,fLogicZ);
864
865 ::chart::XTransformation2* pTransformation = pSubPosHelper->getTransformationScaledLogicToScene();
866
867 //transformation 3) -> 4)
868 drawing::Position3D aTransformedBottom ( pTransformation->transform( aLogicBottom ) );
869 drawing::Position3D aTransformedLeftBottomFront ( pTransformation->transform( aLogicLeftBottomFront ) );
870 drawing::Position3D aTransformedRightDeepTop ( pTransformation->transform( aLogicRightDeepTop ) );
871 drawing::Position3D aTransformedTopTop ( pTransformation->transform( aLogicTopTop ) );
872
873 drawing::Direction3D aSize = aTransformedRightDeepTop - aTransformedLeftBottomFront;
874 drawing::Direction3D aTopSize( aTransformedTopTop - aTransformedRightDeepTop );
875 fTopHeight = aTopSize.DirectionY;
876
877 sal_Int32 nRotateZAngleHundredthDegree = 0;
878 if( pPosHelper->isSwapXAndY() )
879 {
880 fTopHeight = aTopSize.DirectionX;
881 nRotateZAngleHundredthDegree = 90*100;
882 aSize = drawing::Direction3D(aSize.DirectionY,aSize.DirectionX,aSize.DirectionZ);
883 }
884
885 if( aSize.DirectionX < 0 )
886 aSize.DirectionX *= -1.0;
887 if( aSize.DirectionZ < 0 )
888 aSize.DirectionZ *= -1.0;
889 if( fTopHeight < 0 )
890 fTopHeight *= -1.0;
891
892 xShape = createDataPoint3D_Bar(
893 xSeriesGroupShape_Shapes, aTransformedBottom, aSize, fTopHeight, nRotateZAngleHundredthDegree
894 , xDataPointProperties, nGeometry3D );
895 }
896 else //m_nDimension!=3
897 {
898 drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
899 drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
900 std::vector<std::vector<css::drawing::Position3D>> aPoly
901 {
902 { // inner vector
903 drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
904 drawing::Position3D( fLogicX+fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
905 aRightUpperPoint,
906 aLeftUpperPoint,
907 drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ)
908 }
909 };
910 pPosHelper->transformScaledLogicToScene( aPoly );
911 xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes, aPoly );
912 PropertyMapper::setMappedProperties( *xShape, xDataPointProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
913 }
914
915 if(bHasFillColorMapping)
916 {
917 double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
918 if(!std::isnan(nPropVal))
919 {
920 xShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
921 }
922 }
923 //set name/classified ObjectID (CID)
924 ShapeFactory::setShapeName(xShape
925 , ObjectIdentifier::createPointCID(
926 pSeries->getPointCID_Stub(),nPointIndex) );
927 }
928
929 //create error bar
930 createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nPointIndex, m_xLogicTarget, &fLogicX );
931
932 //create data point label
933 if( pSeries->getDataPointLabelIfLabel(nPointIndex) )
934 {
935 double fLogicSum = aLogicYSumMap[nAttachedAxisIndex];
936
938 sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, pPosHelper->isSwapXAndY() );
939
940 double fLowerBarDepth = fLogicBarDepth;
941 double fUpperBarDepth = fLogicBarDepth;
942 {
943 if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
944 {
945 double fOuterBarDepth = fLogicBarDepth * fTopHeight/(fabs(fCompleteHeight));
946 fLowerBarDepth = (fBaseValue < fUpperYValue) ? fabs(fLogicBarDepth) : fabs(fOuterBarDepth);
947 fUpperBarDepth = (fBaseValue < fUpperYValue) ? fabs(fOuterBarDepth) : fabs(fLogicBarDepth);
948 }
949 }
950
951 awt::Point aScreenPosition2D = getLabelScreenPositionAndAlignment(
952 eAlignment, nLabelPlacement, fLogicX, fLowerYValue, fUpperYValue, fLogicZ,
953 fLowerBarDepth, fUpperBarDepth, fBaseValue, pPosHelper);
954 sal_Int32 nOffset = 0;
955 if(eAlignment!=LABEL_ALIGN_CENTER)
956 {
957 nOffset = 100;//add some spacing //@todo maybe get more intelligent values
958 if( m_nDimension == 3 )
959 nOffset = 260;
960 }
961 createDataLabel(
962 xTextTarget, *pSeries, nPointIndex,
963 fLogicValueForLabeDisplay, fLogicSum, aScreenPosition2D, eAlignment, nOffset);
964 }
965
966 }//end iteration through partial points
967
968 }//next series in x slot (next y slot)
969}
970
971} //namespace chart
972
973/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void SuspendReportingDirtyRects()
BarChart()=delete
virtual double getScaledSlotPos(double fCategoryX, double fSeriesNumber) const override
void updateSeriesCount(double fSeriesCount)
void setOuterDistance(double fOuterDistance)
void setInnerDistance(double fInnerDistance)
css::awt::Point transformSceneToScreenPosition(const css::drawing::Position3D &rScenePosition3D) const
bool isStrongLowerRequested(sal_Int32 nDimensionIndex) const
virtual ::chart::XTransformation2 * getTransformationScaledLogicToScene() const
bool isSameForGivenResolution(double fX, double fY, double fZ, double fX2, double fY2, double fZ2)
css::drawing::Direction3D getScaledLogicWidth() const
bool clipYRange(double &rMin, double &rMax) const
void setCoordinateSystemResolution(const css::uno::Sequence< sal_Int32 > &rCoordinateSystemResolution)
::basegfx::B2DRectangle getScaledLogicClipDoubleRect() const
void doLogicScaling(double *pX, double *pY, double *pZ) const
virtual css::drawing::Position3D transformScaledLogicToScene(double fX, double fY, double fZ, bool bClip) const
A list of series that have the same CoordinateSystem.
std::vector< std::unique_ptr< VDataSeries > > m_aSeriesVector
sal_Int32 getAttachedAxisIndex() const
bool getGroupBarsPerAxis() const
allows the transformation of numeric values from one coordinate-system into another.
virtual css::drawing::Position3D transform(const css::drawing::Position3D &rSourceValues) const =0
transforms the given input data tuple, given in the source coordinate system, according to the intern...
#define TOOLS_WARN_EXCEPTION(area, stream)
Reference< XInterface > xTarget
OOO_DLLPUBLIC_CHARTTOOLS void AddPointToPoly(css::drawing::PolyPolygonShape3D &rPoly, const css::drawing::Position3D &rPos, sal_Int32 nSequenceIndex=0)
PolyPolygonShape3D + drawing::Position3D -> PolyPolygonShape3D.
@ LABEL_ALIGN_CENTER
@ LABEL_ALIGN_TOP
@ LABEL_ALIGN_RIGHT
@ LABEL_ALIGN_LEFT
@ LABEL_ALIGN_BOTTOM
const sal_Int32 MAIN_AXIS_INDEX
SVXCORE_DLLPUBLIC E3dScene * DynCastE3dScene(SdrObject *)