1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "oox/drawingml/chart/plotareaconverter.hxx"
25 
26 #include <com/sun/star/chart/XChartDocument.hpp>
27 #include <com/sun/star/chart/XDiagramPositioning.hpp>
28 #include <com/sun/star/chart2/XChartDocument.hpp>
29 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
30 #include <com/sun/star/chart2/XDiagram.hpp>
31 #include <com/sun/star/drawing/Direction3D.hpp>
32 #include <com/sun/star/drawing/ProjectionMode.hpp>
33 #include <com/sun/star/drawing/ShadeMode.hpp>
34 #include "oox/drawingml/chart/axisconverter.hxx"
35 #include "oox/drawingml/chart/plotareamodel.hxx"
36 #include "oox/drawingml/chart/typegroupconverter.hxx"
37 
38 namespace oox {
39 namespace drawingml {
40 namespace chart {
41 
42 // ============================================================================
43 
44 using namespace ::com::sun::star::awt;
45 using namespace ::com::sun::star::chart2;
46 using namespace ::com::sun::star::uno;
47 
48 using ::rtl::OUString;
49 
50 // ============================================================================
51 
52 namespace {
53 
54 /** Axes set model. This is a helper for the plot area converter collecting all
55     type groups and axes of the primary or secondary axes set. */
56 struct AxesSetModel
57 {
58     typedef ModelVector< TypeGroupModel >       TypeGroupVector;
59     typedef ModelMap< sal_Int32, AxisModel >    AxisMap;
60 
61     TypeGroupVector     maTypeGroups;       /// All type groups containing data series.
62     AxisMap             maAxes;             /// All axes mapped by API axis type.
63 
AxesSetModeloox::drawingml::chart::__anonca8a56ca0111::AxesSetModel64     inline explicit     AxesSetModel() {}
~AxesSetModeloox::drawingml::chart::__anonca8a56ca0111::AxesSetModel65     inline              ~AxesSetModel() {}
66 };
67 
68 // ============================================================================
69 
70 /** Axes set converter. This is a helper class for the plot area converter. */
71 class AxesSetConverter : public ConverterBase< AxesSetModel >
72 {
73 public:
74     explicit            AxesSetConverter( const ConverterRoot& rParent, AxesSetModel& rModel );
75     virtual             ~AxesSetConverter();
76 
77     /** Converts the axes set model to a chart2 diagram. Returns an automatic
78         chart title from a single series title, if possible. */
79     void                convertFromModel(
80                             const Reference< XDiagram >& rxDiagram,
81                             View3DModel& rView3DModel,
82                             sal_Int32 nAxesSetIdx,
83                             bool bSupportsVaryColorsByPoint );
84 
85     /** Returns the automatic chart title if the axes set contains only one series. */
getAutomaticTitle() const86     inline const ::rtl::OUString& getAutomaticTitle() const { return maAutoTitle; }
87     /** Returns true, if the chart is three-dimensional. */
is3dChart() const88     inline bool         is3dChart() const { return mb3dChart; }
89     /** Returns true, if chart type supports wall and floor format in 3D mode. */
isWall3dChart() const90     inline bool         isWall3dChart() const { return mbWall3dChart; }
91     /** Returns true, if chart is a pie chart or doughnut chart. */
isPieChart() const92     inline bool         isPieChart() const { return mbPieChart; }
93 
94 private:
95     ::rtl::OUString     maAutoTitle;
96     bool                mb3dChart;
97     bool                mbWall3dChart;
98     bool                mbPieChart;
99 };
100 
101 // ----------------------------------------------------------------------------
102 
AxesSetConverter(const ConverterRoot & rParent,AxesSetModel & rModel)103 AxesSetConverter::AxesSetConverter( const ConverterRoot& rParent, AxesSetModel& rModel ) :
104     ConverterBase< AxesSetModel >( rParent, rModel ),
105     mb3dChart( false ),
106     mbWall3dChart( false ),
107     mbPieChart( false )
108 {
109 }
110 
~AxesSetConverter()111 AxesSetConverter::~AxesSetConverter()
112 {
113 }
114 
lclGetOrCreateAxis(const AxesSetModel::AxisMap & rFromAxes,sal_Int32 nAxisIdx,sal_Int32 nDefTypeId)115 ModelRef< AxisModel > lclGetOrCreateAxis( const AxesSetModel::AxisMap& rFromAxes, sal_Int32 nAxisIdx, sal_Int32 nDefTypeId )
116 {
117     ModelRef< AxisModel > xAxis = rFromAxes.get( nAxisIdx );
118     if( !xAxis )
119         xAxis.create( nDefTypeId ).mbDeleted = true;  // missing axis is invisible
120     return xAxis;
121 }
122 
convertFromModel(const Reference<XDiagram> & rxDiagram,View3DModel & rView3DModel,sal_Int32 nAxesSetIdx,bool bSupportsVaryColorsByPoint)123 void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram,
124         View3DModel& rView3DModel, sal_Int32 nAxesSetIdx, bool bSupportsVaryColorsByPoint )
125 {
126     // create type group converter objects for all type groups
127     typedef RefVector< TypeGroupConverter > TypeGroupConvVector;
128     TypeGroupConvVector aTypeGroups;
129     for( AxesSetModel::TypeGroupVector::iterator aIt = mrModel.maTypeGroups.begin(), aEnd = mrModel.maTypeGroups.end(); aIt != aEnd; ++aIt )
130         aTypeGroups.push_back( TypeGroupConvVector::value_type( new TypeGroupConverter( *this, **aIt ) ) );
131 
132     OSL_ENSURE( !aTypeGroups.empty(), "AxesSetConverter::convertFromModel - no type groups in axes set" );
133     if( !aTypeGroups.empty() ) try
134     {
135         // first type group needed for coordinate system and axis conversion
136         TypeGroupConverter& rFirstTypeGroup = *aTypeGroups.front();
137 
138         // get automatic chart title, if there is only one type group
139         if( aTypeGroups.size() == 1 )
140             maAutoTitle = rFirstTypeGroup.getSingleSeriesTitle();
141 
142         /*  Create a coordinate system. For now, all type groups from all axes sets
143             have to be inserted into one coordinate system. Later, chart2 should
144             support using one coordinate system for each axes set. */
145         Reference< XCoordinateSystem > xCoordSystem;
146         Reference< XCoordinateSystemContainer > xCoordSystemCont( rxDiagram, UNO_QUERY_THROW );
147         Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems();
148         if( aCoordSystems.hasElements() )
149         {
150             OSL_ENSURE( aCoordSystems.getLength() == 1, "AxesSetConverter::convertFromModel - too many coordinate systems" );
151             xCoordSystem = aCoordSystems[ 0 ];
152             OSL_ENSURE( xCoordSystem.is(), "AxesSetConverter::convertFromModel - invalid coordinate system" );
153         }
154         else
155         {
156             xCoordSystem = rFirstTypeGroup.createCoordinateSystem();
157             if( xCoordSystem.is() )
158                 xCoordSystemCont->addCoordinateSystem( xCoordSystem );
159         }
160 
161         // 3D view settings
162         mb3dChart = rFirstTypeGroup.is3dChart();
163         mbWall3dChart = rFirstTypeGroup.isWall3dChart();
164         mbPieChart = rFirstTypeGroup.getTypeInfo().meTypeCategory == TYPECATEGORY_PIE;
165         if( mb3dChart )
166         {
167             View3DConverter aView3DConv( *this, rView3DModel );
168             aView3DConv.convertFromModel( rxDiagram, rFirstTypeGroup );
169         }
170 
171         /*  Convert all chart type groups. Each type group will add its series
172             to the data provider attached to the chart document. */
173         if( xCoordSystem.is() )
174         {
175             // convert all axes (create missing axis models)
176             ModelRef< AxisModel > xXAxis = lclGetOrCreateAxis( mrModel.maAxes, API_X_AXIS, rFirstTypeGroup.getTypeInfo().mbCategoryAxis ? C_TOKEN( catAx ) : C_TOKEN( valAx ) );
177             ModelRef< AxisModel > xYAxis = lclGetOrCreateAxis( mrModel.maAxes, API_Y_AXIS, C_TOKEN( valAx ) );
178 
179             AxisConverter aXAxisConv( *this, *xXAxis );
180             aXAxisConv.convertFromModel( xCoordSystem, rFirstTypeGroup, xYAxis.get(), nAxesSetIdx, API_X_AXIS );
181             AxisConverter aYAxisConv( *this, *xYAxis );
182             aYAxisConv.convertFromModel( xCoordSystem, rFirstTypeGroup, xXAxis.get(), nAxesSetIdx, API_Y_AXIS );
183 
184             if( rFirstTypeGroup.isDeep3dChart() )
185             {
186                 ModelRef< AxisModel > xZAxis = lclGetOrCreateAxis( mrModel.maAxes, API_Z_AXIS, C_TOKEN( serAx ) );
187                 AxisConverter aZAxisConv( *this, *xZAxis );
188                 aZAxisConv.convertFromModel( xCoordSystem, rFirstTypeGroup, 0, nAxesSetIdx, API_Z_AXIS );
189             }
190 
191             // convert all chart type groups, this converts all series data and formatting
192             for( TypeGroupConvVector::iterator aTIt = aTypeGroups.begin(), aTEnd = aTypeGroups.end(); aTIt != aTEnd; ++aTIt )
193                 (*aTIt)->convertFromModel( rxDiagram, xCoordSystem, nAxesSetIdx, bSupportsVaryColorsByPoint );
194         }
195     }
196     catch( Exception& )
197     {
198     }
199 }
200 
201 } // namespace
202 
203 // ============================================================================
204 
View3DConverter(const ConverterRoot & rParent,View3DModel & rModel)205 View3DConverter::View3DConverter( const ConverterRoot& rParent, View3DModel& rModel ) :
206     ConverterBase< View3DModel >( rParent, rModel )
207 {
208 }
209 
~View3DConverter()210 View3DConverter::~View3DConverter()
211 {
212 }
213 
convertFromModel(const Reference<XDiagram> & rxDiagram,TypeGroupConverter & rTypeGroup)214 void View3DConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, TypeGroupConverter& rTypeGroup )
215 {
216     namespace cssd = ::com::sun::star::drawing;
217     PropertySet aPropSet( rxDiagram );
218 
219     sal_Int32 nRotationY = 0;
220     sal_Int32 nRotationX = 0;
221     bool bRightAngled = false;
222     sal_Int32 nAmbientColor = 0;
223     sal_Int32 nLightColor = 0;
224 
225     if( rTypeGroup.getTypeInfo().meTypeCategory == TYPECATEGORY_PIE )
226     {
227         // Y rotation used as 'first pie slice angle' in 3D pie charts
228         rTypeGroup.convertPieRotation( aPropSet, mrModel.monRotationY.get( 0 ) );
229         // X rotation a.k.a. elevation (map OOXML [0..90] to Chart2 [-90,0])
230         nRotationX = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.monRotationX.get( 15 ), 0, 90 ) - 90;
231         // no right-angled axes in pie charts
232         bRightAngled = false;
233         // ambient color (Gray 30%)
234         nAmbientColor = 0xB3B3B3;
235         // light color (Gray 70%)
236         nLightColor = 0x4C4C4C;
237     }
238     else // 3D bar/area/line charts
239     {
240         // Y rotation (OOXML [0..359], Chart2 [-179,180])
241         nRotationY = mrModel.monRotationY.get( 20 );
242         // X rotation a.k.a. elevation (OOXML [-90..90], Chart2 [-179,180])
243         nRotationX = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.monRotationX.get( 15 ), -90, 90 );
244         // right-angled axes
245         bRightAngled = mrModel.mbRightAngled;
246         // ambient color (Gray 20%)
247         nAmbientColor = 0xCCCCCC;
248         // light color (Gray 60%)
249         nLightColor = 0x666666;
250     }
251 
252     // Y rotation (map OOXML [0..359] to Chart2 [-179,180])
253     nRotationY %= 360;
254     if( nRotationY > 180 ) nRotationY -= 360;
255     /*  Perspective (map OOXML [0..200] to Chart2 [0,100]). Seems that MSO 2007 is
256         buggy here, the XML plugin of MSO 2003 writes the correct perspective in
257         the range from 0 to 100. We will emulate the wrong behaviour of MSO 2007. */
258     sal_Int32 nPerspective = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.mnPerspective / 2, 0, 100 );
259     // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%)
260     bool bParallel = bRightAngled || (nPerspective == 0);
261     cssd::ProjectionMode eProjMode = bParallel ? cssd::ProjectionMode_PARALLEL : cssd::ProjectionMode_PERSPECTIVE;
262 
263     // set rotation properties
264     aPropSet.setProperty( PROP_RotationVertical, nRotationY );
265     aPropSet.setProperty( PROP_RotationHorizontal, nRotationX );
266     aPropSet.setProperty( PROP_Perspective, nPerspective );
267     aPropSet.setProperty( PROP_RightAngledAxes, bRightAngled );
268     aPropSet.setProperty( PROP_D3DScenePerspective, eProjMode );
269 
270     // set light settings
271     aPropSet.setProperty( PROP_D3DSceneShadeMode, cssd::ShadeMode_FLAT );
272     aPropSet.setProperty( PROP_D3DSceneAmbientColor, nAmbientColor );
273     aPropSet.setProperty( PROP_D3DSceneLightOn1, false );
274     aPropSet.setProperty( PROP_D3DSceneLightOn2, true );
275     aPropSet.setProperty( PROP_D3DSceneLightColor2, nLightColor );
276     aPropSet.setProperty( PROP_D3DSceneLightDirection2, cssd::Direction3D( 0.2, 0.4, 1.0 ) );
277 }
278 
279 // ============================================================================
280 
WallFloorConverter(const ConverterRoot & rParent,WallFloorModel & rModel)281 WallFloorConverter::WallFloorConverter( const ConverterRoot& rParent, WallFloorModel& rModel ) :
282     ConverterBase< WallFloorModel >( rParent, rModel )
283 {
284 }
285 
~WallFloorConverter()286 WallFloorConverter::~WallFloorConverter()
287 {
288 }
289 
convertFromModel(const Reference<XDiagram> & rxDiagram,ObjectType eObjType)290 void WallFloorConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, ObjectType eObjType )
291 {
292     if( rxDiagram.is() )
293     {
294         PropertySet aPropSet;
295         switch( eObjType )
296         {
297             case OBJECTTYPE_FLOOR:  aPropSet.set( rxDiagram->getFloor() );  break;
298             case OBJECTTYPE_WALL:   aPropSet.set( rxDiagram->getWall() );   break;
299             default:                OSL_ENSURE( false, "WallFloorConverter::convertFromModel - invalid object type" );
300         }
301         if( aPropSet.is() )
302             getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(), eObjType );
303     }
304 }
305 
306 // ============================================================================
307 
PlotAreaConverter(const ConverterRoot & rParent,PlotAreaModel & rModel)308 PlotAreaConverter::PlotAreaConverter( const ConverterRoot& rParent, PlotAreaModel& rModel ) :
309     ConverterBase< PlotAreaModel >( rParent, rModel ),
310     mb3dChart( false ),
311     mbWall3dChart( false ),
312     mbPieChart( false )
313 {
314 }
315 
~PlotAreaConverter()316 PlotAreaConverter::~PlotAreaConverter()
317 {
318 }
319 
convertFromModel(View3DModel & rView3DModel)320 void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel )
321 {
322     /*  Create the diagram object and attach it to the chart document. One
323         diagram is used to carry all coordinate systems and data series. */
324     Reference< XDiagram > xDiagram;
325     try
326     {
327         xDiagram.set( createInstance( CREATE_OUSTRING( "com.sun.star.chart2.Diagram" ) ), UNO_QUERY_THROW );
328         getChartDocument()->setFirstDiagram( xDiagram );
329     }
330     catch( Exception& )
331     {
332     }
333 
334     // store all axis models in a map, keyed by axis identifier
335     typedef ModelMap< sal_Int32, AxisModel > AxisMap;
336     AxisMap aAxisMap;
337     for( PlotAreaModel::AxisVector::iterator aAIt = mrModel.maAxes.begin(), aAEnd = mrModel.maAxes.end(); aAIt != aAEnd; ++aAIt )
338     {
339         PlotAreaModel::AxisVector::value_type xAxis = *aAIt;
340         OSL_ENSURE( xAxis->mnAxisId >= 0, "PlotAreaConverter::convertFromModel - invalid axis identifier" );
341         OSL_ENSURE( !aAxisMap.has( xAxis->mnAxisId ), "PlotAreaConverter::convertFromModel - axis identifiers not unique" );
342         if( xAxis->mnAxisId >= 0 )
343             aAxisMap[ xAxis->mnAxisId ] = xAxis;
344     }
345 
346     // group the type group models into different axes sets
347     typedef ModelVector< AxesSetModel > AxesSetVector;
348     AxesSetVector aAxesSets;
349     sal_Int32 nMaxSeriesIdx = -1;
350     for( PlotAreaModel::TypeGroupVector::iterator aTIt = mrModel.maTypeGroups.begin(), aTEnd = mrModel.maTypeGroups.end(); aTIt != aTEnd; ++aTIt )
351     {
352         PlotAreaModel::TypeGroupVector::value_type xTypeGroup = *aTIt;
353         if( !xTypeGroup->maSeries.empty() )
354         {
355             // try to find a compatible axes set for the type group
356             AxesSetModel* pAxesSet = 0;
357             for( AxesSetVector::iterator aASIt = aAxesSets.begin(), aASEnd = aAxesSets.end(); !pAxesSet && (aASIt != aASEnd); ++aASIt )
358                 if( (*aASIt)->maTypeGroups.front()->maAxisIds == xTypeGroup->maAxisIds )
359                     pAxesSet = aASIt->get();
360 
361             // not possible to insert into an existing axes set -> start a new axes set
362             if( !pAxesSet )
363             {
364                 pAxesSet = &aAxesSets.create();
365                 // find axis models used by the type group
366                 const TypeGroupModel::AxisIdVector& rAxisIds = xTypeGroup->maAxisIds;
367                 if( rAxisIds.size() >= 1 )
368                     pAxesSet->maAxes[ API_X_AXIS ] = aAxisMap.get( rAxisIds[ 0 ] );
369                 if( rAxisIds.size() >= 2 )
370                     pAxesSet->maAxes[ API_Y_AXIS ] = aAxisMap.get( rAxisIds[ 1 ] );
371                 if( rAxisIds.size() >= 3 )
372                     pAxesSet->maAxes[ API_Z_AXIS ] = aAxisMap.get( rAxisIds[ 2 ] );
373             }
374 
375             // insert the type group model
376             pAxesSet->maTypeGroups.push_back( xTypeGroup );
377 
378             // collect the maximum series index for automatic series formatting
379             for( TypeGroupModel::SeriesVector::iterator aSIt = xTypeGroup->maSeries.begin(), aSEnd = xTypeGroup->maSeries.end(); aSIt != aSEnd; ++aSIt )
380                 nMaxSeriesIdx = ::std::max( nMaxSeriesIdx, (*aSIt)->mnIndex );
381         }
382     }
383     getFormatter().setMaxSeriesIndex( nMaxSeriesIdx );
384 
385     // varying point colors only for single series in single chart type
386     bool bSupportsVaryColorsByPoint = mrModel.maTypeGroups.size() == 1;
387 
388     // convert all axes sets
389     for( AxesSetVector::iterator aASBeg = aAxesSets.begin(), aASIt = aASBeg, aASEnd = aAxesSets.end(); aASIt != aASEnd; ++aASIt )
390     {
391         AxesSetConverter aAxesSetConv( *this, **aASIt );
392         sal_Int32 nAxesSetIdx = static_cast< sal_Int32 >( aASIt - aASBeg );
393         aAxesSetConv.convertFromModel( xDiagram, rView3DModel, nAxesSetIdx, bSupportsVaryColorsByPoint );
394         if( nAxesSetIdx == 0 )
395         {
396             maAutoTitle = aAxesSetConv.getAutomaticTitle();
397             mb3dChart = aAxesSetConv.is3dChart();
398             mbWall3dChart = aAxesSetConv.isWall3dChart();
399             mbPieChart = aAxesSetConv.isPieChart();
400         }
401         else
402         {
403             maAutoTitle = OUString();
404         }
405     }
406 
407     // plot area formatting
408     if( xDiagram.is() && !mb3dChart )
409     {
410         PropertySet aPropSet( xDiagram->getWall() );
411         getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, OBJECTTYPE_PLOTAREA2D );
412     }
413 }
414 
convertPositionFromModel()415 void PlotAreaConverter::convertPositionFromModel()
416 {
417     LayoutModel& rLayout = mrModel.mxLayout.getOrCreate();
418     LayoutConverter aLayoutConv( *this, rLayout );
419     Rectangle aDiagramRect;
420     if( aLayoutConv.calcAbsRectangle( aDiagramRect ) ) try
421     {
422         namespace cssc = ::com::sun::star::chart;
423         Reference< cssc::XChartDocument > xChart1Doc( getChartDocument(), UNO_QUERY_THROW );
424         Reference< cssc::XDiagramPositioning > xPositioning( xChart1Doc->getDiagram(), UNO_QUERY_THROW );
425         // for pie charts, always set inner plot area size to exclude the data labels as Excel does
426         sal_Int32 nTarget = (mbPieChart && (rLayout.mnTarget == XML_outer)) ? XML_inner : rLayout.mnTarget;
427         switch( nTarget )
428         {
429             case XML_inner:
430                 xPositioning->setDiagramPositionExcludingAxes( aDiagramRect );
431             break;
432             case XML_outer:
433                 xPositioning->setDiagramPositionIncludingAxes( aDiagramRect );
434             break;
435             default:
436                 OSL_ENSURE( false, "PlotAreaConverter::convertPositionFromModel - unknown positioning target" );
437         }
438     }
439     catch( Exception& )
440     {
441     }
442 }
443 
444 // ============================================================================
445 
446 } // namespace chart
447 } // namespace drawingml
448 } // namespace oox
449