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