1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <drawingml/chart/plotareaconverter.hxx>
22 #include <com/sun/star/chart/XChartDocument.hpp>
23 #include <com/sun/star/chart/XDiagramPositioning.hpp>
24 #include <com/sun/star/chart2/XChartDocument.hpp>
25 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
26 #include <com/sun/star/chart2/XDiagram.hpp>
27 #include <com/sun/star/drawing/Direction3D.hpp>
28 #include <com/sun/star/drawing/ProjectionMode.hpp>
29 #include <com/sun/star/drawing/ShadeMode.hpp>
30 #include <osl/diagnose.h>
31 #include <drawingml/chart/datatableconverter.hxx>
32 #include <drawingml/chart/axisconverter.hxx>
33 #include <drawingml/chart/plotareamodel.hxx>
34 #include <drawingml/chart/typegroupconverter.hxx>
35 #include <oox/core/xmlfilterbase.hxx>
36 #include <oox/token/namespaces.hxx>
37 #include <oox/token/properties.hxx>
38 #include <oox/token/tokens.hxx>
39 #include <tools/helpers.hxx>
41 namespace oox::drawingml::chart
{
43 using namespace ::com::sun::star
;
44 using namespace ::com::sun::star::chart2
;
45 using namespace ::com::sun::star::uno
;
49 /** Axes set model. This is a helper for the plot area converter collecting all
50 type groups and axes of the primary or secondary axes set. */
53 typedef ModelVector
< TypeGroupModel
> TypeGroupVector
;
54 typedef ModelMap
< sal_Int32
, AxisModel
> AxisMap
;
56 TypeGroupVector maTypeGroups
; /// All type groups containing data series.
57 AxisMap maAxes
; /// All axes mapped by API axis type.
59 explicit AxesSetModel() {}
62 /** Axes set converter. This is a helper class for the plot area converter. */
63 class AxesSetConverter
: public ConverterBase
< AxesSetModel
>
66 explicit AxesSetConverter( const ConverterRoot
& rParent
, AxesSetModel
& rModel
);
68 /** Converts the axes set model to a chart2 diagram. Returns an automatic
69 chart title from a single series title, if possible. */
70 void convertFromModel(
71 const Reference
< XDiagram
>& rxDiagram
,
72 View3DModel
& rView3DModel
,
73 sal_Int32 nAxesSetIdx
,
74 bool bSupportsVaryColorsByPoint
,
75 bool bUseFixedInnerSize
);
77 /** Returns the automatic chart title if the axes set contains only one series. */
78 const OUString
& getAutomaticTitle() const { return maAutoTitle
; }
79 /** Returns true, if the chart is three-dimensional. */
80 bool is3dChart() const { return mb3dChart
; }
81 /** Returns true, if chart type supports wall and floor format in 3D mode. */
82 bool isWall3dChart() const { return mbWall3dChart
; }
83 /** Returns true, if chart is a pie chart or doughnut chart. */
84 bool isPieChart() const { return mbPieChart
; }
93 AxesSetConverter::AxesSetConverter( const ConverterRoot
& rParent
, AxesSetModel
& rModel
) :
94 ConverterBase
< AxesSetModel
>( rParent
, rModel
),
96 mbWall3dChart( false ),
101 ModelRef
< AxisModel
> lclGetOrCreateAxis( const AxesSetModel::AxisMap
& rFromAxes
, sal_Int32 nAxisIdx
, sal_Int32 nDefTypeId
, bool bMSO2007Doc
)
103 ModelRef
< AxisModel
> xAxis
= rFromAxes
.get( nAxisIdx
);
105 xAxis
.create( nDefTypeId
, bMSO2007Doc
).mbDeleted
= true; // missing axis is invisible
109 void AxesSetConverter::convertFromModel( const Reference
< XDiagram
>& rxDiagram
,
110 View3DModel
& rView3DModel
, sal_Int32 nAxesSetIdx
,
111 bool bSupportsVaryColorsByPoint
, bool bUseFixedInnerSize
)
113 // create type group converter objects for all type groups
114 typedef RefVector
< TypeGroupConverter
> TypeGroupConvVector
;
115 TypeGroupConvVector aTypeGroups
;
116 for (auto const& typeGroup
: mrModel
.maTypeGroups
)
117 aTypeGroups
.push_back( std::make_shared
<TypeGroupConverter
>( *this, *typeGroup
) );
119 OSL_ENSURE( !aTypeGroups
.empty(), "AxesSetConverter::convertFromModel - no type groups in axes set" );
120 if( aTypeGroups
.empty() )
125 // first type group needed for coordinate system and axis conversion
126 TypeGroupConverter
& rFirstTypeGroup
= *aTypeGroups
.front();
128 // get automatic chart title, if there is only one type group
129 if( aTypeGroups
.size() == 1 )
130 maAutoTitle
= rFirstTypeGroup
.getSingleSeriesTitle();
132 /* Create a coordinate system. For now, all type groups from all axes sets
133 have to be inserted into one coordinate system. Later, chart2 should
134 support using one coordinate system for each axes set. */
135 Reference
< XCoordinateSystem
> xCoordSystem
;
136 Reference
< XCoordinateSystemContainer
> xCoordSystemCont( rxDiagram
, UNO_QUERY_THROW
);
137 Sequence
< Reference
< XCoordinateSystem
> > aCoordSystems
= xCoordSystemCont
->getCoordinateSystems();
138 if( aCoordSystems
.hasElements() )
140 OSL_ENSURE( aCoordSystems
.getLength() == 1, "AxesSetConverter::convertFromModel - too many coordinate systems" );
141 xCoordSystem
= aCoordSystems
[ 0 ];
142 OSL_ENSURE( xCoordSystem
.is(), "AxesSetConverter::convertFromModel - invalid coordinate system" );
146 xCoordSystem
= rFirstTypeGroup
.createCoordinateSystem();
147 if( xCoordSystem
.is() )
148 xCoordSystemCont
->addCoordinateSystem( xCoordSystem
);
152 mb3dChart
= rFirstTypeGroup
.is3dChart();
153 mbWall3dChart
= rFirstTypeGroup
.isWall3dChart();
154 mbPieChart
= rFirstTypeGroup
.getTypeInfo().meTypeCategory
== TYPECATEGORY_PIE
;
157 View3DConverter
aView3DConv( *this, rView3DModel
);
158 aView3DConv
.convertFromModel( rxDiagram
, rFirstTypeGroup
);
161 /* Convert all chart type groups. Each type group will add its series
162 to the data provider attached to the chart document. */
163 if( xCoordSystem
.is() )
165 bool bMSO2007Doc
= getFilter().isMSO2007Document();
166 // convert all axes (create missing axis models)
167 ModelRef
< AxisModel
> xXAxis
= lclGetOrCreateAxis( mrModel
.maAxes
, API_X_AXIS
, rFirstTypeGroup
.getTypeInfo().mbCategoryAxis
? C_TOKEN( catAx
) : C_TOKEN( valAx
), bMSO2007Doc
);
168 ModelRef
< AxisModel
> xYAxis
= lclGetOrCreateAxis( mrModel
.maAxes
, API_Y_AXIS
, C_TOKEN( valAx
), bMSO2007Doc
);
170 AxisConverter
aXAxisConv( *this, *xXAxis
);
171 aXAxisConv
.convertFromModel(xCoordSystem
, aTypeGroups
, xYAxis
.get(), nAxesSetIdx
,
172 API_X_AXIS
, bUseFixedInnerSize
);
173 AxisConverter
aYAxisConv( *this, *xYAxis
);
174 aYAxisConv
.convertFromModel(xCoordSystem
, aTypeGroups
, xXAxis
.get(), nAxesSetIdx
,
175 API_Y_AXIS
, bUseFixedInnerSize
);
177 if( rFirstTypeGroup
.isDeep3dChart() )
179 ModelRef
< AxisModel
> xZAxis
= lclGetOrCreateAxis( mrModel
.maAxes
, API_Z_AXIS
, C_TOKEN( serAx
), bMSO2007Doc
);
180 AxisConverter
aZAxisConv( *this, *xZAxis
);
181 aZAxisConv
.convertFromModel(xCoordSystem
, aTypeGroups
, nullptr, nAxesSetIdx
,
182 API_Z_AXIS
, bUseFixedInnerSize
);
185 // convert all chart type groups, this converts all series data and formatting
186 for (auto const& typeGroup
: aTypeGroups
)
187 typeGroup
->convertFromModel( rxDiagram
, xCoordSystem
, nAxesSetIdx
, bSupportsVaryColorsByPoint
);
197 View3DConverter::View3DConverter( const ConverterRoot
& rParent
, View3DModel
& rModel
) :
198 ConverterBase
< View3DModel
>( rParent
, rModel
)
202 View3DConverter::~View3DConverter()
206 void View3DConverter::convertFromModel( const Reference
< XDiagram
>& rxDiagram
, TypeGroupConverter
const & rTypeGroup
)
208 namespace cssd
= ::com::sun::star::drawing
;
209 PropertySet
aPropSet( rxDiagram
);
211 sal_Int32 nRotationY
= 0;
212 sal_Int32 nRotationX
= 0;
213 bool bRightAngled
= false;
214 sal_Int32 nAmbientColor
= 0;
215 sal_Int32 nLightColor
= 0;
217 if( rTypeGroup
.getTypeInfo().meTypeCategory
== TYPECATEGORY_PIE
)
219 // Y rotation used as 'first pie slice angle' in 3D pie charts
220 rTypeGroup
.convertPieRotation( aPropSet
, mrModel
.monRotationY
.value_or( 0 ) );
221 // X rotation a.k.a. elevation (map OOXML [0..90] to Chart2 [-90,0])
222 nRotationX
= getLimitedValue
< sal_Int32
, sal_Int32
>( mrModel
.monRotationX
.value_or( 15 ), 0, 90 ) - 90;
223 // no right-angled axes in pie charts
224 bRightAngled
= false;
225 // ambient color (Gray 30%)
226 nAmbientColor
= 0xB3B3B3;
227 // light color (Gray 70%)
228 nLightColor
= 0x4C4C4C;
230 else // 3D bar/area/line charts
232 // Y rotation (OOXML [0..359], Chart2 [-179,180])
233 nRotationY
= mrModel
.monRotationY
.value_or( 20 );
234 // X rotation a.k.a. elevation (OOXML [-90..90], Chart2 [-179,180])
235 nRotationX
= getLimitedValue
< sal_Int32
, sal_Int32
>( mrModel
.monRotationX
.value_or( 15 ), -90, 90 );
237 bRightAngled
= mrModel
.mbRightAngled
;
238 // ambient color (Gray 20%)
239 nAmbientColor
= 0xCCCCCC;
240 // light color (Gray 60%)
241 nLightColor
= 0x666666;
244 // Y rotation (map OOXML [0..359] to Chart2 [-179,180])
245 nRotationY
= NormAngle180(nRotationY
);
246 /* Perspective (map OOXML [0..200] to Chart2 [0,100]). Seems that MSO 2007 is
247 buggy here, the XML plugin of MSO 2003 writes the correct perspective in
248 the range from 0 to 100. We will emulate the wrong behaviour of MSO 2007. */
249 sal_Int32 nPerspective
= getLimitedValue
< sal_Int32
, sal_Int32
>( mrModel
.mnPerspective
/ 2, 0, 100 );
250 // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%)
251 bool bParallel
= bRightAngled
|| (nPerspective
== 0);
252 cssd::ProjectionMode eProjMode
= bParallel
? cssd::ProjectionMode_PARALLEL
: cssd::ProjectionMode_PERSPECTIVE
;
254 // set rotation properties
255 aPropSet
.setProperty( PROP_RightAngledAxes
, bRightAngled
);
256 aPropSet
.setProperty( PROP_RotationVertical
, nRotationY
);
257 aPropSet
.setProperty( PROP_RotationHorizontal
, nRotationX
);
258 aPropSet
.setProperty( PROP_Perspective
, nPerspective
);
259 aPropSet
.setProperty( PROP_D3DScenePerspective
, eProjMode
);
261 // set light settings
262 aPropSet
.setProperty( PROP_D3DSceneShadeMode
, cssd::ShadeMode_FLAT
);
263 aPropSet
.setProperty( PROP_D3DSceneAmbientColor
, nAmbientColor
);
264 aPropSet
.setProperty( PROP_D3DSceneLightOn1
, false );
265 aPropSet
.setProperty( PROP_D3DSceneLightOn2
, true );
266 aPropSet
.setProperty( PROP_D3DSceneLightColor2
, nLightColor
);
267 aPropSet
.setProperty( PROP_D3DSceneLightDirection2
, cssd::Direction3D( 0.2, 0.4, 1.0 ) );
270 WallFloorConverter::WallFloorConverter( const ConverterRoot
& rParent
, WallFloorModel
& rModel
) :
271 ConverterBase
< WallFloorModel
>( rParent
, rModel
)
275 WallFloorConverter::~WallFloorConverter()
279 void WallFloorConverter::convertFromModel( const Reference
< XDiagram
>& rxDiagram
, ObjectType eObjType
)
281 bool bMSO2007Doc
= getFilter().isMSO2007Document();
284 PropertySet aPropSet
;
287 case OBJECTTYPE_FLOOR
: aPropSet
.set( rxDiagram
->getFloor() ); break;
288 case OBJECTTYPE_WALL
: aPropSet
.set( rxDiagram
->getWall() ); break;
289 default: OSL_FAIL( "WallFloorConverter::convertFromModel - invalid object type" );
292 getFormatter().convertFrameFormatting( aPropSet
, mrModel
.mxShapeProp
, mrModel
.mxPicOptions
.getOrCreate(bMSO2007Doc
), eObjType
);
296 PlotAreaConverter::PlotAreaConverter( const ConverterRoot
& rParent
, PlotAreaModel
& rModel
) :
297 ConverterBase
< PlotAreaModel
>( rParent
, rModel
),
299 mbWall3dChart( false ),
304 PlotAreaConverter::~PlotAreaConverter()
308 void PlotAreaConverter::convertFromModel( View3DModel
& rView3DModel
)
310 /* Create the diagram object and attach it to the chart document. One
311 diagram is used to carry all coordinate systems and data series. */
312 Reference
< XDiagram
> xDiagram
;
315 xDiagram
.set( createInstance( "com.sun.star.chart2.Diagram" ), UNO_QUERY_THROW
);
316 getChartDocument()->setFirstDiagram( xDiagram
);
322 // store all axis models in a map, keyed by axis identifier
323 typedef ModelMap
< sal_Int32
, AxisModel
> AxisMap
;
325 std::vector
<sal_Int32
>rValAxisIds
;
326 std::vector
<sal_Int32
>rRealValAxisIds
;
328 for (auto const& atypeGroup
: mrModel
.maTypeGroups
)
330 if (atypeGroup
->maAxisIds
.size() > 1)
332 // let's collect which axId belongs to the Y Axis according to maTypeGroups
333 rRealValAxisIds
.push_back(atypeGroup
->maAxisIds
[1]);
337 for (auto const& axis
: mrModel
.maAxes
)
339 OSL_ENSURE( axis
->mnAxisId
>= 0, "PlotAreaConverter::convertFromModel - invalid axis identifier" );
340 OSL_ENSURE( !aAxisMap
.has( axis
->mnAxisId
), "PlotAreaConverter::convertFromModel - axis identifiers not unique" );
341 if( axis
->mnAxisId
!= -1 )
342 aAxisMap
[ axis
->mnAxisId
] = axis
;
344 if ( axis
->mnAxisId
!= -1 && axis
->mnTypeId
== C_TOKEN(valAx
) )
346 for (size_t i
= 0; i
< rRealValAxisIds
.size(); i
++)
348 if (axis
->mnAxisId
== rRealValAxisIds
[i
])
350 // let's collect which axId belongs to the Y Axis according to maAxes
351 rValAxisIds
.push_back(axis
->mnAxisId
);
357 // group the type group models into different axes sets
358 typedef ModelVector
< AxesSetModel
> AxesSetVector
;
359 AxesSetVector aAxesSets
;
360 sal_Int32 nMaxSeriesIdx
= -1;
361 for (auto const& typeGroup
: mrModel
.maTypeGroups
)
363 if( !typeGroup
->maSeries
.empty() )
365 // try to find a compatible axes set for the type group
366 AxesSetModel
* pAxesSet
= nullptr;
367 for (auto const& axesSet
: aAxesSets
)
369 if( axesSet
->maTypeGroups
.front()->maAxisIds
== typeGroup
->maAxisIds
)
371 pAxesSet
= axesSet
.get();
377 // not possible to insert into an existing axes set -> start a new axes set
380 pAxesSet
= &aAxesSets
.create();
381 // find axis models used by the type group
382 const std::vector
<sal_Int32
>& rAxisIds
= typeGroup
->maAxisIds
;
383 if( !rAxisIds
.empty() )
384 pAxesSet
->maAxes
[ API_X_AXIS
] = aAxisMap
.get( rAxisIds
[ 0 ] );
385 if( rAxisIds
.size() >= 2 )
386 pAxesSet
->maAxes
[ API_Y_AXIS
] = aAxisMap
.get( rAxisIds
[ 1 ] );
387 if( rAxisIds
.size() >= 3 )
388 pAxesSet
->maAxes
[ API_Z_AXIS
] = aAxisMap
.get( rAxisIds
[ 2 ] );
391 // insert the type group model
392 pAxesSet
->maTypeGroups
.push_back( typeGroup
);
394 // collect the maximum series index for automatic series formatting
395 for (auto const& elemSeries
: typeGroup
->maSeries
)
396 nMaxSeriesIdx
= ::std::max( nMaxSeriesIdx
, elemSeries
->mnIndex
);
399 getFormatter().setMaxSeriesIndex( nMaxSeriesIdx
);
401 // varying point colors only for single series in single chart type
402 bool bSupportsVaryColorsByPoint
= mrModel
.maTypeGroups
.size() == 1;
404 bool bIsCombinedChart
= mrModel
.maTypeGroups
.size() == 2 &&
405 mrModel
.maTypeGroups
[0]->mnTypeId
!= mrModel
.maTypeGroups
[1]->mnTypeId
;
407 // convert all axes sets, and check which axis is attached to the first maTypeGroups
408 sal_Int32 nStartAxesSetIdx
= bIsCombinedChart
? ((rValAxisIds
.size() > 1 && aAxesSets
.size() > 0 && aAxesSets
[0]->maAxes
.count( API_Y_AXIS
)
409 && aAxesSets
[0]->maAxes
[ API_Y_AXIS
]->mnAxisId
!= rValAxisIds
[0] ) ? 1 : 0)
411 sal_Int32 nAxesSetIdx
= nStartAxesSetIdx
;
413 bool bUseFixedInnerSize
= false;
414 if (mrModel
.mxLayout
&& !mrModel
.mxLayout
->mbAutoLayout
)
415 bUseFixedInnerSize
= mrModel
.mxLayout
->mnTarget
== XML_inner
;
417 for (auto const& axesSet
: aAxesSets
)
419 AxesSetConverter
aAxesSetConv(*this, *axesSet
);
420 aAxesSetConv
.convertFromModel(xDiagram
, rView3DModel
, nAxesSetIdx
,
421 bSupportsVaryColorsByPoint
, bUseFixedInnerSize
);
422 if(nAxesSetIdx
== nStartAxesSetIdx
)
424 maAutoTitle
= aAxesSetConv
.getAutomaticTitle();
425 mb3dChart
= aAxesSetConv
.is3dChart();
426 mbWall3dChart
= aAxesSetConv
.isWall3dChart();
427 mbPieChart
= aAxesSetConv
.isPieChart();
433 nAxesSetIdx
= 1 - nAxesSetIdx
;
436 if (mrModel
.mxDataTable
)
438 DataTableConverter
dataTableConverter(*this, *mrModel
.mxDataTable
);
439 dataTableConverter
.convertFromModel(xDiagram
);
442 // plot area formatting
443 if( xDiagram
.is() && !mb3dChart
)
445 PropertySet
aPropSet( xDiagram
->getWall() );
446 getFormatter().convertFrameFormatting( aPropSet
, mrModel
.mxShapeProp
, OBJECTTYPE_PLOTAREA2D
);
450 void PlotAreaConverter::convertPositionFromModel()
452 LayoutModel
& rLayout
= mrModel
.mxLayout
.getOrCreate();
453 LayoutConverter
aLayoutConv( *this, rLayout
);
454 awt::Rectangle aDiagramRect
;
455 if( !aLayoutConv
.calcAbsRectangle( aDiagramRect
) )
460 namespace cssc
= ::com::sun::star::chart
;
461 Reference
< cssc::XChartDocument
> xChart1Doc( getChartDocument(), UNO_QUERY_THROW
);
462 Reference
< cssc::XDiagramPositioning
> xPositioning( xChart1Doc
->getDiagram(), UNO_QUERY_THROW
);
463 // for pie charts, always set inner plot area size to exclude the data labels as Excel does
464 sal_Int32 nTarget
= (mbPieChart
&& (rLayout
.mnTarget
== XML_outer
)) ? XML_inner
: rLayout
.mnTarget
;
468 xPositioning
->setDiagramPositionExcludingAxes( aDiagramRect
);
471 xPositioning
->setDiagramPositionIncludingAxes( aDiagramRect
);
474 OSL_FAIL( "PlotAreaConverter::convertPositionFromModel - unknown positioning target" );
484 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */