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::__anon2aab86b90111::AxesSetModel64 inline explicit AxesSetModel() {}
~AxesSetModeloox::drawingml::chart::__anon2aab86b90111::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