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/axisconverter.hxx" 29 30 #include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp> 31 #include <com/sun/star/chart/ChartAxisLabelPosition.hpp> 32 #include <com/sun/star/chart/ChartAxisMarkPosition.hpp> 33 #include <com/sun/star/chart/ChartAxisPosition.hpp> 34 #include <com/sun/star/chart/TimeInterval.hpp> 35 #include <com/sun/star/chart/TimeUnit.hpp> 36 #include <com/sun/star/chart2/AxisType.hpp> 37 #include <com/sun/star/chart2/TickmarkStyle.hpp> 38 #include <com/sun/star/chart2/XAxis.hpp> 39 #include <com/sun/star/chart2/XCoordinateSystem.hpp> 40 #include <com/sun/star/chart2/XTitled.hpp> 41 #include "oox/drawingml/chart/axismodel.hxx" 42 #include "oox/drawingml/chart/titleconverter.hxx" 43 #include "oox/drawingml/chart/typegroupconverter.hxx" 44 #include "oox/drawingml/lineproperties.hxx" 45 46 namespace oox { 47 namespace drawingml { 48 namespace chart { 49 50 // ============================================================================ 51 52 using namespace ::com::sun::star::beans; 53 using namespace ::com::sun::star::chart2; 54 using namespace ::com::sun::star::uno; 55 56 using ::rtl::OUString; 57 58 // ============================================================================ 59 60 namespace { 61 62 inline void lclSetValueOrClearAny( Any& orAny, const OptValue< double >& rofValue ) 63 { 64 if( rofValue.has() ) orAny <<= rofValue.get(); else orAny.clear(); 65 } 66 67 bool lclIsLogarithmicScale( const AxisModel& rAxisModel ) 68 { 69 return rAxisModel.mofLogBase.has() && (2.0 <= rAxisModel.mofLogBase.get()) && (rAxisModel.mofLogBase.get() <= 1000.0); 70 } 71 72 sal_Int32 lclGetApiTimeUnit( sal_Int32 nTimeUnit ) 73 { 74 using namespace ::com::sun::star::chart; 75 switch( nTimeUnit ) 76 { 77 case XML_days: return TimeUnit::DAY; 78 case XML_months: return TimeUnit::MONTH; 79 case XML_years: return TimeUnit::YEAR; 80 default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" ); 81 } 82 return TimeUnit::DAY; 83 } 84 85 void lclConvertTimeInterval( Any& orInterval, const OptValue< double >& rofUnit, sal_Int32 nTimeUnit ) 86 { 87 if( rofUnit.has() && (1.0 <= rofUnit.get()) && (rofUnit.get() <= SAL_MAX_INT32) ) 88 orInterval <<= ::com::sun::star::chart::TimeInterval( static_cast< sal_Int32 >( rofUnit.get() ), lclGetApiTimeUnit( nTimeUnit ) ); 89 else 90 orInterval.clear(); 91 } 92 93 ::com::sun::star::chart::ChartAxisLabelPosition lclGetLabelPosition( sal_Int32 nToken ) 94 { 95 using namespace ::com::sun::star::chart; 96 switch( nToken ) 97 { 98 case XML_high: return ChartAxisLabelPosition_OUTSIDE_END; 99 case XML_low: return ChartAxisLabelPosition_OUTSIDE_START; 100 case XML_nextTo: return ChartAxisLabelPosition_NEAR_AXIS; 101 } 102 return ChartAxisLabelPosition_NEAR_AXIS; 103 } 104 105 sal_Int32 lclGetTickMark( sal_Int32 nToken ) 106 { 107 using namespace ::com::sun::star::chart2::TickmarkStyle; 108 switch( nToken ) 109 { 110 case XML_in: return INNER; 111 case XML_out: return OUTER; 112 case XML_cross: return INNER | OUTER; 113 } 114 return NONE; 115 } 116 117 } // namespace 118 119 // ============================================================================ 120 121 AxisConverter::AxisConverter( const ConverterRoot& rParent, AxisModel& rModel ) : 122 ConverterBase< AxisModel >( rParent, rModel ) 123 { 124 } 125 126 AxisConverter::~AxisConverter() 127 { 128 } 129 130 void AxisConverter::convertFromModel( const Reference< XCoordinateSystem >& rxCoordSystem, 131 TypeGroupConverter& rTypeGroup, const AxisModel* pCrossingAxis, sal_Int32 nAxesSetIdx, sal_Int32 nAxisIdx ) 132 { 133 Reference< XAxis > xAxis; 134 try 135 { 136 namespace cssc = ::com::sun::star::chart; 137 namespace cssc2 = ::com::sun::star::chart2; 138 139 const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo(); 140 ObjectFormatter& rFormatter = getFormatter(); 141 142 // create the axis object (always) 143 xAxis.set( createInstance( CREATE_OUSTRING( "com.sun.star.chart2.Axis" ) ), UNO_QUERY_THROW ); 144 PropertySet aAxisProp( xAxis ); 145 // #i58688# axis enabled 146 aAxisProp.setProperty( PROP_Show, !mrModel.mbDeleted ); 147 148 // axis line, tick, and gridline properties --------------------------- 149 150 // show axis labels 151 aAxisProp.setProperty( PROP_DisplayLabels, mrModel.mnTickLabelPos != XML_none ); 152 aAxisProp.setProperty( PROP_LabelPosition, lclGetLabelPosition( mrModel.mnTickLabelPos ) ); 153 // no X axis line in radar charts 154 if( (nAxisIdx == API_X_AXIS) && (rTypeInfo.meTypeCategory == TYPECATEGORY_RADAR) ) 155 mrModel.mxShapeProp.getOrCreate().getLineProperties().maLineFill.moFillType = XML_noFill; 156 // axis line and tick label formatting 157 rFormatter.convertFormatting( aAxisProp, mrModel.mxShapeProp, mrModel.mxTextProp, OBJECTTYPE_AXIS ); 158 // tick label rotation 159 rFormatter.convertTextRotation( aAxisProp, mrModel.mxTextProp, true ); 160 161 // tick mark style 162 aAxisProp.setProperty( PROP_MajorTickmarks, lclGetTickMark( mrModel.mnMajorTickMark ) ); 163 aAxisProp.setProperty( PROP_MinorTickmarks, lclGetTickMark( mrModel.mnMinorTickMark ) ); 164 aAxisProp.setProperty( PROP_MarkPosition, cssc::ChartAxisMarkPosition_AT_AXIS ); 165 166 // main grid 167 PropertySet aGridProp( xAxis->getGridProperties() ); 168 aGridProp.setProperty( PROP_Show, mrModel.mxMajorGridLines.is() ); 169 if( mrModel.mxMajorGridLines.is() ) 170 rFormatter.convertFrameFormatting( aGridProp, mrModel.mxMajorGridLines, OBJECTTYPE_MAJORGRIDLINE ); 171 172 // sub grid 173 Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); 174 if( aSubGridPropSeq.hasElements() ) 175 { 176 PropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); 177 aSubGridProp.setProperty( PROP_Show, mrModel.mxMinorGridLines.is() ); 178 if( mrModel.mxMinorGridLines.is() ) 179 rFormatter.convertFrameFormatting( aSubGridProp, mrModel.mxMinorGridLines, OBJECTTYPE_MINORGRIDLINE ); 180 } 181 182 // axis type and X axis categories ------------------------------------ 183 184 ScaleData aScaleData = xAxis->getScaleData(); 185 // set axis type 186 switch( nAxisIdx ) 187 { 188 case API_X_AXIS: 189 if( rTypeInfo.mbCategoryAxis ) 190 { 191 OSL_ENSURE( (mrModel.mnTypeId == C_TOKEN( catAx )) || (mrModel.mnTypeId == C_TOKEN( dateAx )), 192 "AxisConverter::convertFromModel - unexpected axis model type (must: c:catAx or c:dateAx)" ); 193 bool bDateAxis = mrModel.mnTypeId == C_TOKEN( dateAx ); 194 /* Chart2 requires axis type CATEGORY for automatic 195 category/date axis (even if it is a date axis 196 currently). */ 197 aScaleData.AxisType = (bDateAxis && !mrModel.mbAuto) ? cssc2::AxisType::DATE : cssc2::AxisType::CATEGORY; 198 aScaleData.AutoDateAxis = mrModel.mbAuto; 199 aScaleData.Categories = rTypeGroup.createCategorySequence(); 200 } 201 else 202 { 203 OSL_ENSURE( mrModel.mnTypeId == C_TOKEN( valAx ), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" ); 204 aScaleData.AxisType = cssc2::AxisType::REALNUMBER; 205 } 206 break; 207 case API_Y_AXIS: 208 OSL_ENSURE( mrModel.mnTypeId == C_TOKEN( valAx ), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" ); 209 aScaleData.AxisType = rTypeGroup.isPercent() ? cssc2::AxisType::PERCENT : cssc2::AxisType::REALNUMBER; 210 break; 211 case API_Z_AXIS: 212 OSL_ENSURE( mrModel.mnTypeId == C_TOKEN( serAx ), "AxisConverter::convertFromModel - unexpected axis model type (must: c:serAx)" ); 213 OSL_ENSURE( rTypeGroup.isDeep3dChart(), "AxisConverter::convertFromModel - series axis not supported by this chart type" ); 214 aScaleData.AxisType = cssc2::AxisType::SERIES; 215 break; 216 } 217 218 // axis scaling and increment ----------------------------------------- 219 220 switch( aScaleData.AxisType ) 221 { 222 case cssc2::AxisType::CATEGORY: 223 case cssc2::AxisType::SERIES: 224 case cssc2::AxisType::DATE: 225 { 226 /* Determine date axis type from XML type identifier, and not 227 via aScaleData.AxisType, as this value sticks to CATEGORY 228 for automatic category/date axes). */ 229 if( mrModel.mnTypeId == C_TOKEN( dateAx ) ) 230 { 231 // scaling algorithm 232 aScaleData.Scaling.set( createInstance( CREATE_OUSTRING( "com.sun.star.chart2.LinearScaling" ) ), UNO_QUERY ); 233 // min/max 234 lclSetValueOrClearAny( aScaleData.Minimum, mrModel.mofMin ); 235 lclSetValueOrClearAny( aScaleData.Maximum, mrModel.mofMax ); 236 // major/minor increment 237 lclConvertTimeInterval( aScaleData.TimeIncrement.MajorTimeInterval, mrModel.mofMajorUnit, mrModel.mnMajorTimeUnit ); 238 lclConvertTimeInterval( aScaleData.TimeIncrement.MinorTimeInterval, mrModel.mofMinorUnit, mrModel.mnMinorTimeUnit ); 239 // base time unit 240 if( mrModel.monBaseTimeUnit.has() ) 241 aScaleData.TimeIncrement.TimeResolution <<= lclGetApiTimeUnit( mrModel.monBaseTimeUnit.get() ); 242 else 243 aScaleData.TimeIncrement.TimeResolution.clear(); 244 } 245 else 246 { 247 // do not overlap text unless all labels are visible 248 aAxisProp.setProperty( PROP_TextOverlap, mrModel.mnTickLabelSkip == 1 ); 249 // do not break text into several lines 250 aAxisProp.setProperty( PROP_TextBreak, false ); 251 // do not stagger labels in two lines 252 aAxisProp.setProperty( PROP_ArrangeOrder, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE ); 253 //! TODO #i58731# show n-th category 254 } 255 } 256 break; 257 case cssc2::AxisType::REALNUMBER: 258 case cssc2::AxisType::PERCENT: 259 { 260 // scaling algorithm 261 bool bLogScale = lclIsLogarithmicScale( mrModel ); 262 OUString aScalingService = bLogScale ? 263 CREATE_OUSTRING( "com.sun.star.chart2.LogarithmicScaling" ) : 264 CREATE_OUSTRING( "com.sun.star.chart2.LinearScaling" ); 265 aScaleData.Scaling.set( createInstance( aScalingService ), UNO_QUERY ); 266 // min/max 267 lclSetValueOrClearAny( aScaleData.Minimum, mrModel.mofMin ); 268 lclSetValueOrClearAny( aScaleData.Maximum, mrModel.mofMax ); 269 // major increment 270 IncrementData& rIncrementData = aScaleData.IncrementData; 271 if( mrModel.mofMajorUnit.has() && aScaleData.Scaling.is() ) 272 rIncrementData.Distance <<= aScaleData.Scaling->doScaling( mrModel.mofMajorUnit.get() ); 273 else 274 lclSetValueOrClearAny( rIncrementData.Distance, mrModel.mofMajorUnit ); 275 // minor increment 276 Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; 277 rSubIncrementSeq.realloc( 1 ); 278 Any& rIntervalCount = rSubIncrementSeq[ 0 ].IntervalCount; 279 rIntervalCount.clear(); 280 if( bLogScale ) 281 { 282 if( mrModel.mofMinorUnit.has() ) 283 rIntervalCount <<= sal_Int32( 9 ); 284 } 285 else if( mrModel.mofMajorUnit.has() && mrModel.mofMinorUnit.has() && (0.0 < mrModel.mofMinorUnit.get()) && (mrModel.mofMinorUnit.get() <= mrModel.mofMajorUnit.get()) ) 286 { 287 double fCount = mrModel.mofMajorUnit.get() / mrModel.mofMinorUnit.get() + 0.5; 288 if( (1.0 <= fCount) && (fCount < 1001.0) ) 289 rIntervalCount <<= static_cast< sal_Int32 >( fCount ); 290 } 291 } 292 break; 293 default: 294 OSL_ENSURE( false, "AxisConverter::convertFromModel - unknown axis type" ); 295 } 296 297 /* Do not set a value to the Origin member anymore (already done via 298 new axis properties 'CrossoverPosition' and 'CrossoverValue'). */ 299 aScaleData.Origin.clear(); 300 301 // axis orientation --------------------------------------------------- 302 303 // #i85167# pie/donut charts need opposite direction at Y axis 304 // #i87747# radar charts need opposite direction at X axis 305 bool bMirrorDirection = 306 ((nAxisIdx == API_Y_AXIS) && (rTypeInfo.meTypeCategory == TYPECATEGORY_PIE)) || 307 ((nAxisIdx == API_X_AXIS) && (rTypeInfo.meTypeCategory == TYPECATEGORY_RADAR)); 308 bool bReverse = (mrModel.mnOrientation == XML_maxMin) != bMirrorDirection; 309 aScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; 310 311 // write back scaling data 312 xAxis->setScaleData( aScaleData ); 313 314 // number format ------------------------------------------------------ 315 316 if( (aScaleData.AxisType == cssc2::AxisType::REALNUMBER) || (aScaleData.AxisType == cssc2::AxisType::PERCENT) ) 317 getFormatter().convertNumberFormat( aAxisProp, mrModel.maNumberFormat ); 318 319 // position of crossing axis ------------------------------------------ 320 321 bool bManualCrossing = mrModel.mofCrossesAt.has(); 322 cssc::ChartAxisPosition eAxisPos = cssc::ChartAxisPosition_VALUE; 323 if( !bManualCrossing ) switch( mrModel.mnCrossMode ) 324 { 325 case XML_min: eAxisPos = cssc::ChartAxisPosition_START; break; 326 case XML_max: eAxisPos = cssc::ChartAxisPosition_END; break; 327 case XML_autoZero: eAxisPos = cssc::ChartAxisPosition_VALUE; break; 328 } 329 aAxisProp.setProperty( PROP_CrossoverPosition, eAxisPos ); 330 331 // calculate automatic origin depending on scaling mode of crossing axis 332 bool bCrossingLogScale = pCrossingAxis && lclIsLogarithmicScale( *pCrossingAxis ); 333 double fCrossingPos = bManualCrossing ? mrModel.mofCrossesAt.get() : (bCrossingLogScale ? 1.0 : 0.0); 334 aAxisProp.setProperty( PROP_CrossoverValue, fCrossingPos ); 335 336 // axis title --------------------------------------------------------- 337 338 // in radar charts, title objects may exist, but are not shown 339 if( mrModel.mxTitle.is() && (rTypeGroup.getTypeInfo().meTypeCategory != TYPECATEGORY_RADAR) ) 340 { 341 Reference< XTitled > xTitled( xAxis, UNO_QUERY_THROW ); 342 TitleConverter aTitleConv( *this, *mrModel.mxTitle ); 343 aTitleConv.convertFromModel( xTitled, CREATE_OUSTRING( "Axis Title" ), OBJECTTYPE_AXISTITLE, nAxesSetIdx, nAxisIdx ); 344 } 345 } 346 catch( Exception& ) 347 { 348 } 349 350 if( xAxis.is() && rxCoordSystem.is() ) try 351 { 352 // insert axis into coordinate system 353 rxCoordSystem->setAxisByDimension( nAxisIdx, xAxis, nAxesSetIdx ); 354 } 355 catch( Exception& ) 356 { 357 OSL_ENSURE( false, "AxisConverter::convertFromModel - cannot insert axis into coordinate system" ); 358 } 359 } 360 361 // ============================================================================ 362 363 } // namespace chart 364 } // namespace drawingml 365 } // namespace oox 366