1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "drawingml/chart/axisconverter.hxx"
22 #include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp>
23 #include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
24 #include <com/sun/star/chart/ChartAxisMarkPosition.hpp>
25 #include <com/sun/star/chart/ChartAxisPosition.hpp>
26 #include <com/sun/star/chart/TimeInterval.hpp>
27 #include <com/sun/star/chart/TimeUnit.hpp>
28 #include <com/sun/star/chart2/AxisType.hpp>
29 #include <com/sun/star/chart2/TickmarkStyle.hpp>
30 #include <com/sun/star/chart2/LinearScaling.hpp>
31 #include <com/sun/star/chart2/LogarithmicScaling.hpp>
32 #include <com/sun/star/chart2/XAxis.hpp>
33 #include <com/sun/star/chart2/XCoordinateSystem.hpp>
34 #include <com/sun/star/chart2/XTitled.hpp>
35 #include "drawingml/chart/axismodel.hxx"
36 #include "drawingml/chart/titleconverter.hxx"
37 #include "drawingml/chart/typegroupconverter.hxx"
38 #include "drawingml/lineproperties.hxx"
39 #include <oox/token/namespaces.hxx>
40 #include <oox/token/properties.hxx>
41 #include <oox/token/tokens.hxx>
42 #include "comphelper/processfactory.hxx"
43 #include <osl/diagnose.h>
49 using namespace ::com::sun::star::beans
;
50 using namespace ::com::sun::star::chart2
;
51 using namespace ::com::sun::star::uno
;
55 inline void lclSetValueOrClearAny( Any
& orAny
, const OptValue
< double >& rofValue
)
57 if( rofValue
.has() ) orAny
<<= rofValue
.get(); else orAny
.clear();
60 bool lclIsLogarithmicScale( const AxisModel
& rAxisModel
)
62 return rAxisModel
.mofLogBase
.has() && (2.0 <= rAxisModel
.mofLogBase
.get()) && (rAxisModel
.mofLogBase
.get() <= 1000.0);
65 sal_Int32
lclGetApiTimeUnit( sal_Int32 nTimeUnit
)
67 using namespace ::com::sun::star::chart
;
70 case XML_days
: return TimeUnit::DAY
;
71 case XML_months
: return TimeUnit::MONTH
;
72 case XML_years
: return TimeUnit::YEAR
;
73 default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" );
78 void lclConvertTimeInterval( Any
& orInterval
, const OptValue
< double >& rofUnit
, sal_Int32 nTimeUnit
)
80 if( rofUnit
.has() && (1.0 <= rofUnit
.get()) && (rofUnit
.get() <= SAL_MAX_INT32
) )
81 orInterval
<<= css::chart::TimeInterval( static_cast< sal_Int32
>( rofUnit
.get() ), lclGetApiTimeUnit( nTimeUnit
) );
86 css::chart::ChartAxisLabelPosition
lclGetLabelPosition( sal_Int32 nToken
)
88 using namespace ::com::sun::star::chart
;
91 case XML_high
: return ChartAxisLabelPosition_OUTSIDE_END
;
92 case XML_low
: return ChartAxisLabelPosition_OUTSIDE_START
;
93 case XML_nextTo
: return ChartAxisLabelPosition_NEAR_AXIS
;
95 return ChartAxisLabelPosition_NEAR_AXIS
;
98 sal_Int32
lclGetTickMark( sal_Int32 nToken
)
100 using namespace ::com::sun::star::chart2::TickmarkStyle
;
103 case XML_in
: return INNER
;
104 case XML_out
: return OUTER
;
105 case XML_cross
: return INNER
| OUTER
;
107 return css::chart2::TickmarkStyle::NONE
;
111 * The groups is of percent type only when all of its members are of percent
114 bool isPercent( const RefVector
<TypeGroupConverter
>& rTypeGroups
)
116 if (rTypeGroups
.empty())
119 RefVector
<TypeGroupConverter
>::const_iterator it
= rTypeGroups
.begin(), itEnd
= rTypeGroups
.end();
120 for (; it
!= itEnd
; ++it
)
122 TypeGroupConverter
& rConv
= **it
;
123 if (!rConv
.isPercent())
132 AxisConverter::AxisConverter( const ConverterRoot
& rParent
, AxisModel
& rModel
) :
133 ConverterBase
< AxisModel
>( rParent
, rModel
)
137 AxisConverter::~AxisConverter()
141 void AxisConverter::convertFromModel(
142 const Reference
< XCoordinateSystem
>& rxCoordSystem
,
143 RefVector
<TypeGroupConverter
>& rTypeGroups
, const AxisModel
* pCrossingAxis
, sal_Int32 nAxesSetIdx
, sal_Int32 nAxisIdx
)
145 if (rTypeGroups
.empty())
148 Reference
< XAxis
> xAxis
;
151 namespace cssc
= ::com::sun::star::chart
;
152 namespace cssc2
= ::com::sun::star::chart2
;
154 const TypeGroupInfo
& rTypeInfo
= rTypeGroups
.front()->getTypeInfo();
155 ObjectFormatter
& rFormatter
= getFormatter();
157 // create the axis object (always)
158 xAxis
.set( createInstance( "com.sun.star.chart2.Axis" ), UNO_QUERY_THROW
);
159 PropertySet
aAxisProp( xAxis
);
160 // #i58688# axis enabled
161 aAxisProp
.setProperty( PROP_Show
, !mrModel
.mbDeleted
);
163 // axis line, tick, and gridline properties ---------------------------
166 aAxisProp
.setProperty( PROP_DisplayLabels
, mrModel
.mnTickLabelPos
!= XML_none
);
167 aAxisProp
.setProperty( PROP_LabelPosition
, lclGetLabelPosition( mrModel
.mnTickLabelPos
) );
168 // no X axis line in radar charts
169 if( (nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
) )
170 mrModel
.mxShapeProp
.getOrCreate().getLineProperties().maLineFill
.moFillType
= XML_noFill
;
171 // axis line and tick label formatting
172 rFormatter
.convertFormatting( aAxisProp
, mrModel
.mxShapeProp
, mrModel
.mxTextProp
, OBJECTTYPE_AXIS
);
173 // tick label rotation
174 ObjectFormatter::convertTextRotation( aAxisProp
, mrModel
.mxTextProp
, true );
177 aAxisProp
.setProperty( PROP_MajorTickmarks
, lclGetTickMark( mrModel
.mnMajorTickMark
) );
178 aAxisProp
.setProperty( PROP_MinorTickmarks
, lclGetTickMark( mrModel
.mnMinorTickMark
) );
179 aAxisProp
.setProperty( PROP_MarkPosition
, cssc::ChartAxisMarkPosition_AT_AXIS
);
182 PropertySet
aGridProp( xAxis
->getGridProperties() );
183 aGridProp
.setProperty( PROP_Show
, mrModel
.mxMajorGridLines
.is() );
184 if( mrModel
.mxMajorGridLines
.is() )
185 rFormatter
.convertFrameFormatting( aGridProp
, mrModel
.mxMajorGridLines
, OBJECTTYPE_MAJORGRIDLINE
);
188 Sequence
< Reference
< XPropertySet
> > aSubGridPropSeq
= xAxis
->getSubGridProperties();
189 if( aSubGridPropSeq
.hasElements() )
191 PropertySet
aSubGridProp( aSubGridPropSeq
[ 0 ] );
192 aSubGridProp
.setProperty( PROP_Show
, mrModel
.mxMinorGridLines
.is() );
193 if( mrModel
.mxMinorGridLines
.is() )
194 rFormatter
.convertFrameFormatting( aSubGridProp
, mrModel
.mxMinorGridLines
, OBJECTTYPE_MINORGRIDLINE
);
197 // axis type and X axis categories ------------------------------------
199 ScaleData aScaleData
= xAxis
->getScaleData();
204 if( rTypeInfo
.mbCategoryAxis
)
206 OSL_ENSURE( (mrModel
.mnTypeId
== C_TOKEN( catAx
)) || (mrModel
.mnTypeId
== C_TOKEN( dateAx
)),
207 "AxisConverter::convertFromModel - unexpected axis model type (must: c:catAx or c:dateAx)" );
208 bool bDateAxis
= mrModel
.mnTypeId
== C_TOKEN( dateAx
);
209 /* Chart2 requires axis type CATEGORY for automatic
210 category/date axis (even if it is a date axis
212 aScaleData
.AxisType
= (bDateAxis
&& !mrModel
.mbAuto
) ? cssc2::AxisType::DATE
: cssc2::AxisType::CATEGORY
;
213 aScaleData
.AutoDateAxis
= mrModel
.mbAuto
;
214 aScaleData
.Categories
= rTypeGroups
.front()->createCategorySequence();
218 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
219 aScaleData
.AxisType
= cssc2::AxisType::REALNUMBER
;
223 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
224 aScaleData
.AxisType
= isPercent(rTypeGroups
) ? cssc2::AxisType::PERCENT
: cssc2::AxisType::REALNUMBER
;
227 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( serAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:serAx)" );
228 OSL_ENSURE( rTypeGroups
.front()->isDeep3dChart(), "AxisConverter::convertFromModel - series axis not supported by this chart type" );
229 aScaleData
.AxisType
= cssc2::AxisType::SERIES
;
233 // axis scaling and increment -----------------------------------------
235 switch( aScaleData
.AxisType
)
237 case cssc2::AxisType::CATEGORY
:
238 case cssc2::AxisType::SERIES
:
239 case cssc2::AxisType::DATE
:
241 /* Determine date axis type from XML type identifier, and not
242 via aScaleData.AxisType, as this value sticks to CATEGORY
243 for automatic category/date axes). */
244 if( mrModel
.mnTypeId
== C_TOKEN( dateAx
) )
247 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
249 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
250 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
251 // major/minor increment
252 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MajorTimeInterval
, mrModel
.mofMajorUnit
, mrModel
.mnMajorTimeUnit
);
253 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MinorTimeInterval
, mrModel
.mofMinorUnit
, mrModel
.mnMinorTimeUnit
);
255 if( mrModel
.monBaseTimeUnit
.has() )
256 aScaleData
.TimeIncrement
.TimeResolution
<<= lclGetApiTimeUnit( mrModel
.monBaseTimeUnit
.get() );
258 aScaleData
.TimeIncrement
.TimeResolution
.clear();
262 // do not overlap text unless all labels are visible
263 aAxisProp
.setProperty( PROP_TextOverlap
, mrModel
.mnTickLabelSkip
== 1 );
264 // do not break text into several lines
265 aAxisProp
.setProperty( PROP_TextBreak
, false );
266 // do not stagger labels in two lines
267 aAxisProp
.setProperty( PROP_ArrangeOrder
, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE
);
268 //! TODO #i58731# show n-th category
272 case cssc2::AxisType::REALNUMBER
:
273 case cssc2::AxisType::PERCENT
:
276 bool bLogScale
= lclIsLogarithmicScale( mrModel
);
278 aScaleData
.Scaling
= LogarithmicScaling::create( comphelper::getProcessComponentContext() );
280 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
282 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
283 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
285 IncrementData
& rIncrementData
= aScaleData
.IncrementData
;
286 if( mrModel
.mofMajorUnit
.has() && aScaleData
.Scaling
.is() )
287 rIncrementData
.Distance
<<= aScaleData
.Scaling
->doScaling( mrModel
.mofMajorUnit
.get() );
289 lclSetValueOrClearAny( rIncrementData
.Distance
, mrModel
.mofMajorUnit
);
291 Sequence
< SubIncrement
>& rSubIncrementSeq
= rIncrementData
.SubIncrements
;
292 rSubIncrementSeq
.realloc( 1 );
293 Any
& rIntervalCount
= rSubIncrementSeq
[ 0 ].IntervalCount
;
294 rIntervalCount
.clear();
297 if( mrModel
.mofMinorUnit
.has() )
298 rIntervalCount
<<= sal_Int32( 9 );
300 else if( mrModel
.mofMajorUnit
.has() && mrModel
.mofMinorUnit
.has() && (0.0 < mrModel
.mofMinorUnit
.get()) && (mrModel
.mofMinorUnit
.get() <= mrModel
.mofMajorUnit
.get()) )
302 double fCount
= mrModel
.mofMajorUnit
.get() / mrModel
.mofMinorUnit
.get() + 0.5;
303 if( (1.0 <= fCount
) && (fCount
< 1001.0) )
304 rIntervalCount
<<= static_cast< sal_Int32
>( fCount
);
309 OSL_FAIL( "AxisConverter::convertFromModel - unknown axis type" );
312 /* Do not set a value to the Origin member anymore (already done via
313 new axis properties 'CrossoverPosition' and 'CrossoverValue'). */
314 aScaleData
.Origin
.clear();
316 // axis orientation ---------------------------------------------------
318 // #i85167# pie/donut charts need opposite direction at Y axis
319 // #i87747# radar charts need opposite direction at X axis
320 bool bMirrorDirection
=
321 ((nAxisIdx
== API_Y_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
)) ||
322 ((nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
));
323 bool bReverse
= (mrModel
.mnOrientation
== XML_maxMin
) != bMirrorDirection
;
324 aScaleData
.Orientation
= bReverse
? cssc2::AxisOrientation_REVERSE
: cssc2::AxisOrientation_MATHEMATICAL
;
326 // write back scaling data
327 xAxis
->setScaleData( aScaleData
);
329 // number format ------------------------------------------------------
331 if( (aScaleData
.AxisType
== cssc2::AxisType::REALNUMBER
) || (aScaleData
.AxisType
== cssc2::AxisType::PERCENT
) )
332 getFormatter().convertNumberFormat(aAxisProp
, mrModel
.maNumberFormat
, true);
334 // position of crossing axis ------------------------------------------
336 bool bManualCrossing
= mrModel
.mofCrossesAt
.has();
337 cssc::ChartAxisPosition eAxisPos
= cssc::ChartAxisPosition_VALUE
;
338 if( !bManualCrossing
) switch( mrModel
.mnCrossMode
)
340 case XML_min
: eAxisPos
= cssc::ChartAxisPosition_START
; break;
341 case XML_max
: eAxisPos
= cssc::ChartAxisPosition_END
; break;
342 case XML_autoZero
: eAxisPos
= cssc::ChartAxisPosition_ZERO
; break;
344 if( !mrModel
.mbAuto
)
345 aAxisProp
.setProperty( PROP_CrossoverPosition
, eAxisPos
);
347 // calculate automatic origin depending on scaling mode of crossing axis
348 bool bCrossingLogScale
= pCrossingAxis
&& lclIsLogarithmicScale( *pCrossingAxis
);
349 double fCrossingPos
= bManualCrossing
? mrModel
.mofCrossesAt
.get() : (bCrossingLogScale
? 1.0 : 0.0);
350 aAxisProp
.setProperty( PROP_CrossoverValue
, fCrossingPos
);
352 // axis title ---------------------------------------------------------
354 // in radar charts, title objects may exist, but are not shown
355 if( mrModel
.mxTitle
.is() && (rTypeGroups
.front()->getTypeInfo().meTypeCategory
!= TYPECATEGORY_RADAR
) )
357 Reference
< XTitled
> xTitled( xAxis
, UNO_QUERY_THROW
);
358 TitleConverter
aTitleConv( *this, *mrModel
.mxTitle
);
359 aTitleConv
.convertFromModel( xTitled
, "Axis Title", OBJECTTYPE_AXISTITLE
, nAxesSetIdx
, nAxisIdx
);
362 // axis data unit label -----------------------------------------------
363 AxisDispUnitsConverter
axisDispUnitsConverter (*this, mrModel
.mxDispUnits
.getOrCreate());
364 axisDispUnitsConverter
.convertFromModel(xAxis
);
370 if( xAxis
.is() && rxCoordSystem
.is() ) try
372 // insert axis into coordinate system
373 rxCoordSystem
->setAxisByDimension( nAxisIdx
, xAxis
, nAxesSetIdx
);
377 OSL_FAIL( "AxisConverter::convertFromModel - cannot insert axis into coordinate system" );
381 AxisDispUnitsConverter::AxisDispUnitsConverter( const ConverterRoot
& rParent
, AxisDispUnitsModel
& rModel
) :
382 ConverterBase
< AxisDispUnitsModel
>( rParent
, rModel
)
386 AxisDispUnitsConverter::~AxisDispUnitsConverter()
390 void AxisDispUnitsConverter::convertFromModel( const Reference
< XAxis
>& rxAxis
)
392 PropertySet
aPropSet( rxAxis
);
393 if (!(mrModel
.mnBuiltInUnit
).isEmpty() )
395 aPropSet
.setProperty(PROP_DisplayUnits
, true);
396 aPropSet
.setProperty( PROP_BuiltInUnit
, mrModel
.mnBuiltInUnit
);
401 } // namespace drawingml
404 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */