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 64 inline explicit AxesSetModel() {} 65 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. */ 86 inline const ::rtl::OUString& getAutomaticTitle() const { return maAutoTitle; } 87 /** Returns true, if the chart is three-dimensional. */ 88 inline bool is3dChart() const { return mb3dChart; } 89 /** Returns true, if chart type supports wall and floor format in 3D mode. */ 90 inline bool isWall3dChart() const { return mbWall3dChart; } 91 /** Returns true, if chart is a pie chart or doughnut chart. */ 92 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 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 111 AxesSetConverter::~AxesSetConverter() 112 { 113 } 114 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 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 205 View3DConverter::View3DConverter( const ConverterRoot& rParent, View3DModel& rModel ) : 206 ConverterBase< View3DModel >( rParent, rModel ) 207 { 208 } 209 210 View3DConverter::~View3DConverter() 211 { 212 } 213 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 281 WallFloorConverter::WallFloorConverter( const ConverterRoot& rParent, WallFloorModel& rModel ) : 282 ConverterBase< WallFloorModel >( rParent, rModel ) 283 { 284 } 285 286 WallFloorConverter::~WallFloorConverter() 287 { 288 } 289 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 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 316 PlotAreaConverter::~PlotAreaConverter() 317 { 318 } 319 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 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