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 "oox/drawingml/lineproperties.hxx"
39 #include "comphelper/processfactory.hxx"
40 #include <osl/diagnose.h>
46 using namespace ::com::sun::star::beans
;
47 using namespace ::com::sun::star::chart2
;
48 using namespace ::com::sun::star::uno
;
52 inline void lclSetValueOrClearAny( Any
& orAny
, const OptValue
< double >& rofValue
)
54 if( rofValue
.has() ) orAny
<<= rofValue
.get(); else orAny
.clear();
57 bool lclIsLogarithmicScale( const AxisModel
& rAxisModel
)
59 return rAxisModel
.mofLogBase
.has() && (2.0 <= rAxisModel
.mofLogBase
.get()) && (rAxisModel
.mofLogBase
.get() <= 1000.0);
62 sal_Int32
lclGetApiTimeUnit( sal_Int32 nTimeUnit
)
64 using namespace ::com::sun::star::chart
;
67 case XML_days
: return TimeUnit::DAY
;
68 case XML_months
: return TimeUnit::MONTH
;
69 case XML_years
: return TimeUnit::YEAR
;
70 default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" );
75 void lclConvertTimeInterval( Any
& orInterval
, const OptValue
< double >& rofUnit
, sal_Int32 nTimeUnit
)
77 if( rofUnit
.has() && (1.0 <= rofUnit
.get()) && (rofUnit
.get() <= SAL_MAX_INT32
) )
78 orInterval
<<= ::com::sun::star::chart::TimeInterval( static_cast< sal_Int32
>( rofUnit
.get() ), lclGetApiTimeUnit( nTimeUnit
) );
83 ::com::sun::star::chart::ChartAxisLabelPosition
lclGetLabelPosition( sal_Int32 nToken
)
85 using namespace ::com::sun::star::chart
;
88 case XML_high
: return ChartAxisLabelPosition_OUTSIDE_END
;
89 case XML_low
: return ChartAxisLabelPosition_OUTSIDE_START
;
90 case XML_nextTo
: return ChartAxisLabelPosition_NEAR_AXIS
;
92 return ChartAxisLabelPosition_NEAR_AXIS
;
95 sal_Int32
lclGetTickMark( sal_Int32 nToken
)
97 using namespace ::com::sun::star::chart2::TickmarkStyle
;
100 case XML_in
: return INNER
;
101 case XML_out
: return OUTER
;
102 case XML_cross
: return INNER
| OUTER
;
104 return css::chart2::TickmarkStyle::NONE
;
108 * The groups is of percent type only when all of its members are of percent
111 bool isPercent( const RefVector
<TypeGroupConverter
>& rTypeGroups
)
113 if (rTypeGroups
.empty())
116 RefVector
<TypeGroupConverter
>::const_iterator it
= rTypeGroups
.begin(), itEnd
= rTypeGroups
.end();
117 for (; it
!= itEnd
; ++it
)
119 TypeGroupConverter
& rConv
= **it
;
120 if (!rConv
.isPercent())
129 AxisConverter::AxisConverter( const ConverterRoot
& rParent
, AxisModel
& rModel
) :
130 ConverterBase
< AxisModel
>( rParent
, rModel
)
134 AxisConverter::~AxisConverter()
138 void AxisConverter::convertFromModel(
139 const Reference
< XCoordinateSystem
>& rxCoordSystem
,
140 RefVector
<TypeGroupConverter
>& rTypeGroups
, const AxisModel
* pCrossingAxis
, sal_Int32 nAxesSetIdx
, sal_Int32 nAxisIdx
)
142 if (rTypeGroups
.empty())
145 Reference
< XAxis
> xAxis
;
148 namespace cssc
= ::com::sun::star::chart
;
149 namespace cssc2
= ::com::sun::star::chart2
;
151 const TypeGroupInfo
& rTypeInfo
= rTypeGroups
.front()->getTypeInfo();
152 ObjectFormatter
& rFormatter
= getFormatter();
154 // create the axis object (always)
155 xAxis
.set( createInstance( "com.sun.star.chart2.Axis" ), UNO_QUERY_THROW
);
156 PropertySet
aAxisProp( xAxis
);
157 // #i58688# axis enabled
158 aAxisProp
.setProperty( PROP_Show
, !mrModel
.mbDeleted
);
160 // axis line, tick, and gridline properties ---------------------------
163 aAxisProp
.setProperty( PROP_DisplayLabels
, mrModel
.mnTickLabelPos
!= XML_none
);
164 aAxisProp
.setProperty( PROP_LabelPosition
, lclGetLabelPosition( mrModel
.mnTickLabelPos
) );
165 // no X axis line in radar charts
166 if( (nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
) )
167 mrModel
.mxShapeProp
.getOrCreate().getLineProperties().maLineFill
.moFillType
= XML_noFill
;
168 // axis line and tick label formatting
169 rFormatter
.convertFormatting( aAxisProp
, mrModel
.mxShapeProp
, mrModel
.mxTextProp
, OBJECTTYPE_AXIS
);
170 // tick label rotation
171 ObjectFormatter::convertTextRotation( aAxisProp
, mrModel
.mxTextProp
, true );
174 aAxisProp
.setProperty( PROP_MajorTickmarks
, lclGetTickMark( mrModel
.mnMajorTickMark
) );
175 aAxisProp
.setProperty( PROP_MinorTickmarks
, lclGetTickMark( mrModel
.mnMinorTickMark
) );
176 aAxisProp
.setProperty( PROP_MarkPosition
, cssc::ChartAxisMarkPosition_AT_AXIS
);
179 PropertySet
aGridProp( xAxis
->getGridProperties() );
180 aGridProp
.setProperty( PROP_Show
, mrModel
.mxMajorGridLines
.is() );
181 if( mrModel
.mxMajorGridLines
.is() )
182 rFormatter
.convertFrameFormatting( aGridProp
, mrModel
.mxMajorGridLines
, OBJECTTYPE_MAJORGRIDLINE
);
185 Sequence
< Reference
< XPropertySet
> > aSubGridPropSeq
= xAxis
->getSubGridProperties();
186 if( aSubGridPropSeq
.hasElements() )
188 PropertySet
aSubGridProp( aSubGridPropSeq
[ 0 ] );
189 aSubGridProp
.setProperty( PROP_Show
, mrModel
.mxMinorGridLines
.is() );
190 if( mrModel
.mxMinorGridLines
.is() )
191 rFormatter
.convertFrameFormatting( aSubGridProp
, mrModel
.mxMinorGridLines
, OBJECTTYPE_MINORGRIDLINE
);
194 // axis type and X axis categories ------------------------------------
196 ScaleData aScaleData
= xAxis
->getScaleData();
201 if( rTypeInfo
.mbCategoryAxis
)
203 OSL_ENSURE( (mrModel
.mnTypeId
== C_TOKEN( catAx
)) || (mrModel
.mnTypeId
== C_TOKEN( dateAx
)),
204 "AxisConverter::convertFromModel - unexpected axis model type (must: c:catAx or c:dateAx)" );
205 bool bDateAxis
= mrModel
.mnTypeId
== C_TOKEN( dateAx
);
206 /* Chart2 requires axis type CATEGORY for automatic
207 category/date axis (even if it is a date axis
209 aScaleData
.AxisType
= (bDateAxis
&& !mrModel
.mbAuto
) ? cssc2::AxisType::DATE
: cssc2::AxisType::CATEGORY
;
210 aScaleData
.AutoDateAxis
= mrModel
.mbAuto
;
211 aScaleData
.Categories
= rTypeGroups
.front()->createCategorySequence();
215 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
216 aScaleData
.AxisType
= cssc2::AxisType::REALNUMBER
;
220 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
221 aScaleData
.AxisType
= isPercent(rTypeGroups
) ? cssc2::AxisType::PERCENT
: cssc2::AxisType::REALNUMBER
;
224 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( serAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:serAx)" );
225 OSL_ENSURE( rTypeGroups
.front()->isDeep3dChart(), "AxisConverter::convertFromModel - series axis not supported by this chart type" );
226 aScaleData
.AxisType
= cssc2::AxisType::SERIES
;
230 // axis scaling and increment -----------------------------------------
232 switch( aScaleData
.AxisType
)
234 case cssc2::AxisType::CATEGORY
:
235 case cssc2::AxisType::SERIES
:
236 case cssc2::AxisType::DATE
:
238 /* Determine date axis type from XML type identifier, and not
239 via aScaleData.AxisType, as this value sticks to CATEGORY
240 for automatic category/date axes). */
241 if( mrModel
.mnTypeId
== C_TOKEN( dateAx
) )
244 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
246 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
247 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
248 // major/minor increment
249 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MajorTimeInterval
, mrModel
.mofMajorUnit
, mrModel
.mnMajorTimeUnit
);
250 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MinorTimeInterval
, mrModel
.mofMinorUnit
, mrModel
.mnMinorTimeUnit
);
252 if( mrModel
.monBaseTimeUnit
.has() )
253 aScaleData
.TimeIncrement
.TimeResolution
<<= lclGetApiTimeUnit( mrModel
.monBaseTimeUnit
.get() );
255 aScaleData
.TimeIncrement
.TimeResolution
.clear();
259 // do not overlap text unless all labels are visible
260 aAxisProp
.setProperty( PROP_TextOverlap
, mrModel
.mnTickLabelSkip
== 1 );
261 // do not break text into several lines
262 aAxisProp
.setProperty( PROP_TextBreak
, false );
263 // do not stagger labels in two lines
264 aAxisProp
.setProperty( PROP_ArrangeOrder
, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE
);
265 //! TODO #i58731# show n-th category
269 case cssc2::AxisType::REALNUMBER
:
270 case cssc2::AxisType::PERCENT
:
273 bool bLogScale
= lclIsLogarithmicScale( mrModel
);
275 aScaleData
.Scaling
= LogarithmicScaling::create( comphelper::getProcessComponentContext() );
277 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
279 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
280 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
282 IncrementData
& rIncrementData
= aScaleData
.IncrementData
;
283 if( mrModel
.mofMajorUnit
.has() && aScaleData
.Scaling
.is() )
284 rIncrementData
.Distance
<<= aScaleData
.Scaling
->doScaling( mrModel
.mofMajorUnit
.get() );
286 lclSetValueOrClearAny( rIncrementData
.Distance
, mrModel
.mofMajorUnit
);
288 Sequence
< SubIncrement
>& rSubIncrementSeq
= rIncrementData
.SubIncrements
;
289 rSubIncrementSeq
.realloc( 1 );
290 Any
& rIntervalCount
= rSubIncrementSeq
[ 0 ].IntervalCount
;
291 rIntervalCount
.clear();
294 if( mrModel
.mofMinorUnit
.has() )
295 rIntervalCount
<<= sal_Int32( 9 );
297 else if( mrModel
.mofMajorUnit
.has() && mrModel
.mofMinorUnit
.has() && (0.0 < mrModel
.mofMinorUnit
.get()) && (mrModel
.mofMinorUnit
.get() <= mrModel
.mofMajorUnit
.get()) )
299 double fCount
= mrModel
.mofMajorUnit
.get() / mrModel
.mofMinorUnit
.get() + 0.5;
300 if( (1.0 <= fCount
) && (fCount
< 1001.0) )
301 rIntervalCount
<<= static_cast< sal_Int32
>( fCount
);
306 OSL_FAIL( "AxisConverter::convertFromModel - unknown axis type" );
309 /* Do not set a value to the Origin member anymore (already done via
310 new axis properties 'CrossoverPosition' and 'CrossoverValue'). */
311 aScaleData
.Origin
.clear();
313 // axis orientation ---------------------------------------------------
315 // #i85167# pie/donut charts need opposite direction at Y axis
316 // #i87747# radar charts need opposite direction at X axis
317 bool bMirrorDirection
=
318 ((nAxisIdx
== API_Y_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
)) ||
319 ((nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
));
320 bool bReverse
= (mrModel
.mnOrientation
== XML_maxMin
) != bMirrorDirection
;
321 aScaleData
.Orientation
= bReverse
? cssc2::AxisOrientation_REVERSE
: cssc2::AxisOrientation_MATHEMATICAL
;
323 // write back scaling data
324 xAxis
->setScaleData( aScaleData
);
326 // number format ------------------------------------------------------
328 if( (aScaleData
.AxisType
== cssc2::AxisType::REALNUMBER
) || (aScaleData
.AxisType
== cssc2::AxisType::PERCENT
) )
329 getFormatter().convertNumberFormat(aAxisProp
, mrModel
.maNumberFormat
, true);
331 // position of crossing axis ------------------------------------------
333 bool bManualCrossing
= mrModel
.mofCrossesAt
.has();
334 cssc::ChartAxisPosition eAxisPos
= cssc::ChartAxisPosition_VALUE
;
335 if( !bManualCrossing
) switch( mrModel
.mnCrossMode
)
337 case XML_min
: eAxisPos
= cssc::ChartAxisPosition_START
; break;
338 case XML_max
: eAxisPos
= cssc::ChartAxisPosition_END
; break;
339 case XML_autoZero
: eAxisPos
= cssc::ChartAxisPosition_ZERO
; break;
341 if( !mrModel
.mbAuto
)
342 aAxisProp
.setProperty( PROP_CrossoverPosition
, eAxisPos
);
344 // calculate automatic origin depending on scaling mode of crossing axis
345 bool bCrossingLogScale
= pCrossingAxis
&& lclIsLogarithmicScale( *pCrossingAxis
);
346 double fCrossingPos
= bManualCrossing
? mrModel
.mofCrossesAt
.get() : (bCrossingLogScale
? 1.0 : 0.0);
347 aAxisProp
.setProperty( PROP_CrossoverValue
, fCrossingPos
);
349 // axis title ---------------------------------------------------------
351 // in radar charts, title objects may exist, but are not shown
352 if( mrModel
.mxTitle
.is() && (rTypeGroups
.front()->getTypeInfo().meTypeCategory
!= TYPECATEGORY_RADAR
) )
354 Reference
< XTitled
> xTitled( xAxis
, UNO_QUERY_THROW
);
355 TitleConverter
aTitleConv( *this, *mrModel
.mxTitle
);
356 aTitleConv
.convertFromModel( xTitled
, "Axis Title", OBJECTTYPE_AXISTITLE
, nAxesSetIdx
, nAxisIdx
);
359 // axis data unit label -----------------------------------------------
360 AxisDispUnitsConverter
axisDispUnitsConverter (*this, mrModel
.mxDispUnits
.getOrCreate());
361 axisDispUnitsConverter
.convertFromModel(xAxis
);
367 if( xAxis
.is() && rxCoordSystem
.is() ) try
369 // insert axis into coordinate system
370 rxCoordSystem
->setAxisByDimension( nAxisIdx
, xAxis
, nAxesSetIdx
);
374 OSL_FAIL( "AxisConverter::convertFromModel - cannot insert axis into coordinate system" );
378 AxisDispUnitsConverter::AxisDispUnitsConverter( const ConverterRoot
& rParent
, AxisDispUnitsModel
& rModel
) :
379 ConverterBase
< AxisDispUnitsModel
>( rParent
, rModel
)
383 AxisDispUnitsConverter::~AxisDispUnitsConverter()
387 void AxisDispUnitsConverter::convertFromModel( const Reference
< XAxis
>& rxAxis
)
389 PropertySet
aPropSet( rxAxis
);
390 if (!(mrModel
.mnBuiltInUnit
).isEmpty() )
392 aPropSet
.setProperty(PROP_DisplayUnits
, true);
393 aPropSet
.setProperty( PROP_BuiltInUnit
, mrModel
.mnBuiltInUnit
);
398 } // namespace drawingml
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */