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>
21 #include <ooxresid.hxx>
22 #include <strings.hrc>
24 #include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp>
25 #include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
26 #include <com/sun/star/chart/ChartAxisMarkPosition.hpp>
27 #include <com/sun/star/chart/ChartAxisPosition.hpp>
28 #include <com/sun/star/chart/TimeInterval.hpp>
29 #include <com/sun/star/chart/TimeUnit.hpp>
30 #include <com/sun/star/chart2/AxisType.hpp>
31 #include <com/sun/star/chart2/TickmarkStyle.hpp>
32 #include <com/sun/star/chart2/LinearScaling.hpp>
33 #include <com/sun/star/chart2/LogarithmicScaling.hpp>
34 #include <com/sun/star/chart2/XAxis.hpp>
35 #include <com/sun/star/chart2/XCoordinateSystem.hpp>
36 #include <com/sun/star/chart2/XTitled.hpp>
37 #include <drawingml/chart/axismodel.hxx>
38 #include <drawingml/chart/titleconverter.hxx>
39 #include <drawingml/chart/typegroupconverter.hxx>
40 #include <drawingml/lineproperties.hxx>
41 #include <oox/token/namespaces.hxx>
42 #include <oox/token/properties.hxx>
43 #include <oox/token/tokens.hxx>
44 #include <comphelper/processfactory.hxx>
45 #include <osl/diagnose.h>
51 using namespace ::com::sun::star::beans
;
52 using namespace ::com::sun::star::chart2
;
53 using namespace ::com::sun::star::uno
;
57 void lclSetValueOrClearAny( Any
& orAny
, const OptValue
< double >& rofValue
)
59 if( rofValue
.has() ) orAny
<<= rofValue
.get(); else orAny
.clear();
62 bool lclIsLogarithmicScale( const AxisModel
& rAxisModel
)
64 return rAxisModel
.mofLogBase
.has() && (2.0 <= rAxisModel
.mofLogBase
.get()) && (rAxisModel
.mofLogBase
.get() <= 1000.0);
67 sal_Int32
lclGetApiTimeUnit( sal_Int32 nTimeUnit
)
69 using namespace ::com::sun::star::chart
;
72 case XML_days
: return TimeUnit::DAY
;
73 case XML_months
: return TimeUnit::MONTH
;
74 case XML_years
: return TimeUnit::YEAR
;
75 default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" );
80 void lclConvertTimeInterval( Any
& orInterval
, const OptValue
< double >& rofUnit
, sal_Int32 nTimeUnit
)
82 if( rofUnit
.has() && (1.0 <= rofUnit
.get()) && (rofUnit
.get() <= SAL_MAX_INT32
) )
83 orInterval
<<= css::chart::TimeInterval( static_cast< sal_Int32
>( rofUnit
.get() ), lclGetApiTimeUnit( nTimeUnit
) );
88 css::chart::ChartAxisLabelPosition
lclGetLabelPosition( sal_Int32 nToken
)
90 using namespace ::com::sun::star::chart
;
93 case XML_high
: return ChartAxisLabelPosition_OUTSIDE_END
;
94 case XML_low
: return ChartAxisLabelPosition_OUTSIDE_START
;
95 case XML_nextTo
: return ChartAxisLabelPosition_NEAR_AXIS
;
97 return ChartAxisLabelPosition_NEAR_AXIS
;
100 sal_Int32
lclGetTickMark( sal_Int32 nToken
)
102 using namespace ::com::sun::star::chart2::TickmarkStyle
;
105 case XML_in
: return INNER
;
106 case XML_out
: return OUTER
;
107 case XML_cross
: return INNER
| OUTER
;
109 return css::chart2::TickmarkStyle::NONE
;
113 * The groups is of percent type only when all of its members are of percent
116 bool isPercent( const RefVector
<TypeGroupConverter
>& rTypeGroups
)
118 if (rTypeGroups
.empty())
121 for (auto const& typeGroup
: rTypeGroups
)
123 TypeGroupConverter
& rConv
= *typeGroup
;
124 if (!rConv
.isPercent())
133 AxisConverter::AxisConverter( const ConverterRoot
& rParent
, AxisModel
& rModel
) :
134 ConverterBase
< AxisModel
>( rParent
, rModel
)
138 AxisConverter::~AxisConverter()
142 void AxisConverter::convertFromModel(
143 const Reference
< XCoordinateSystem
>& rxCoordSystem
,
144 RefVector
<TypeGroupConverter
>& rTypeGroups
, const AxisModel
* pCrossingAxis
, sal_Int32 nAxesSetIdx
, sal_Int32 nAxisIdx
)
146 if (rTypeGroups
.empty())
149 Reference
< XAxis
> xAxis
;
152 namespace cssc
= ::com::sun::star::chart
;
153 namespace cssc2
= ::com::sun::star::chart2
;
155 const TypeGroupInfo
& rTypeInfo
= rTypeGroups
.front()->getTypeInfo();
156 ObjectFormatter
& rFormatter
= getFormatter();
158 // create the axis object (always)
159 xAxis
.set( createInstance( "com.sun.star.chart2.Axis" ), UNO_QUERY_THROW
);
160 PropertySet
aAxisProp( xAxis
);
161 // #i58688# axis enabled
162 aAxisProp
.setProperty( PROP_Show
, !mrModel
.mbDeleted
);
164 // axis line, tick, and gridline properties ---------------------------
167 aAxisProp
.setProperty( PROP_DisplayLabels
, mrModel
.mnTickLabelPos
!= XML_none
);
168 aAxisProp
.setProperty( PROP_LabelPosition
, lclGetLabelPosition( mrModel
.mnTickLabelPos
) );
169 // no X axis line in radar charts
170 if( (nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
) )
171 mrModel
.mxShapeProp
.getOrCreate().getLineProperties().maLineFill
.moFillType
= XML_noFill
;
172 // axis line and tick label formatting
173 rFormatter
.convertFormatting( aAxisProp
, mrModel
.mxShapeProp
, mrModel
.mxTextProp
, OBJECTTYPE_AXIS
);
174 // tick label rotation
175 ObjectFormatter::convertTextRotation( aAxisProp
, mrModel
.mxTextProp
, true );
178 aAxisProp
.setProperty( PROP_MajorTickmarks
, lclGetTickMark( mrModel
.mnMajorTickMark
) );
179 aAxisProp
.setProperty( PROP_MinorTickmarks
, lclGetTickMark( mrModel
.mnMinorTickMark
) );
180 aAxisProp
.setProperty( PROP_MarkPosition
, cssc::ChartAxisMarkPosition_AT_AXIS
);
183 PropertySet
aGridProp( xAxis
->getGridProperties() );
184 aGridProp
.setProperty( PROP_Show
, mrModel
.mxMajorGridLines
.is() );
185 if( mrModel
.mxMajorGridLines
.is() )
186 rFormatter
.convertFrameFormatting( aGridProp
, mrModel
.mxMajorGridLines
, OBJECTTYPE_MAJORGRIDLINE
);
189 Sequence
< Reference
< XPropertySet
> > aSubGridPropSeq
= xAxis
->getSubGridProperties();
190 if( aSubGridPropSeq
.hasElements() )
192 PropertySet
aSubGridProp( aSubGridPropSeq
[ 0 ] );
193 aSubGridProp
.setProperty( PROP_Show
, mrModel
.mxMinorGridLines
.is() );
194 if( mrModel
.mxMinorGridLines
.is() )
195 rFormatter
.convertFrameFormatting( aSubGridProp
, mrModel
.mxMinorGridLines
, OBJECTTYPE_MINORGRIDLINE
);
198 // axis type and X axis categories ------------------------------------
200 ScaleData aScaleData
= xAxis
->getScaleData();
205 if( rTypeInfo
.mbCategoryAxis
)
207 OSL_ENSURE( (mrModel
.mnTypeId
== C_TOKEN( catAx
)) || (mrModel
.mnTypeId
== C_TOKEN( dateAx
)),
208 "AxisConverter::convertFromModel - unexpected axis model type (must: c:catAx or c:dateAx)" );
209 bool bDateAxis
= mrModel
.mnTypeId
== C_TOKEN( dateAx
);
210 /* Chart2 requires axis type CATEGORY for automatic
211 category/date axis (even if it is a date axis
213 aScaleData
.AxisType
= (bDateAxis
&& !mrModel
.mbAuto
) ? cssc2::AxisType::DATE
: cssc2::AxisType::CATEGORY
;
214 aScaleData
.AutoDateAxis
= mrModel
.mbAuto
;
215 aScaleData
.Categories
= rTypeGroups
.front()->createCategorySequence();
216 /* set default ShiftedCategoryPosition values for some charttype,
217 because the XML can contain wrong CrossBetween value, if came from MSO */
218 if( rTypeGroups
.front()->is3dChart() && (rTypeInfo
.meTypeId
== TYPEID_BAR
|| rTypeInfo
.meTypeId
== TYPEID_HORBAR
|| rTypeInfo
.meTypeId
== TYPEID_STOCK
) )
219 aScaleData
.ShiftedCategoryPosition
= true;
220 else if( rTypeInfo
.meTypeId
== TYPEID_RADARLINE
|| rTypeInfo
.meTypeId
== TYPEID_RADARAREA
)
221 aScaleData
.ShiftedCategoryPosition
= false;
223 aScaleData
.ShiftedCategoryPosition
= pCrossingAxis
->mnCrossBetween
== XML_between
;
227 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
228 aScaleData
.AxisType
= cssc2::AxisType::REALNUMBER
;
232 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( valAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:valAx)" );
233 aScaleData
.AxisType
= isPercent(rTypeGroups
) ? cssc2::AxisType::PERCENT
: cssc2::AxisType::REALNUMBER
;
236 OSL_ENSURE( mrModel
.mnTypeId
== C_TOKEN( serAx
), "AxisConverter::convertFromModel - unexpected axis model type (must: c:serAx)" );
237 OSL_ENSURE( rTypeGroups
.front()->isDeep3dChart(), "AxisConverter::convertFromModel - series axis not supported by this chart type" );
238 aScaleData
.AxisType
= cssc2::AxisType::SERIES
;
242 // axis scaling and increment -----------------------------------------
244 switch( aScaleData
.AxisType
)
246 case cssc2::AxisType::CATEGORY
:
247 case cssc2::AxisType::SERIES
:
248 case cssc2::AxisType::DATE
:
250 /* Determine date axis type from XML type identifier, and not
251 via aScaleData.AxisType, as this value sticks to CATEGORY
252 for automatic category/date axes). */
253 if( mrModel
.mnTypeId
== C_TOKEN( dateAx
) )
256 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
258 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
259 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
260 // major/minor increment
261 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MajorTimeInterval
, mrModel
.mofMajorUnit
, mrModel
.mnMajorTimeUnit
);
262 lclConvertTimeInterval( aScaleData
.TimeIncrement
.MinorTimeInterval
, mrModel
.mofMinorUnit
, mrModel
.mnMinorTimeUnit
);
264 if( mrModel
.monBaseTimeUnit
.has() )
265 aScaleData
.TimeIncrement
.TimeResolution
<<= lclGetApiTimeUnit( mrModel
.monBaseTimeUnit
.get() );
267 aScaleData
.TimeIncrement
.TimeResolution
.clear();
271 // do not overlap text unless all labels are visible
272 aAxisProp
.setProperty( PROP_TextOverlap
, mrModel
.mnTickLabelSkip
== 1 );
273 // do not break text into several lines unless the rotation is 0 degree
274 aAxisProp
.setProperty( PROP_TextBreak
, ObjectFormatter::getTextRotation( mrModel
.mxTextProp
) );
275 // do not stagger labels in two lines
276 aAxisProp
.setProperty( PROP_ArrangeOrder
, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE
);
277 //! TODO #i58731# show n-th category
281 case cssc2::AxisType::REALNUMBER
:
282 case cssc2::AxisType::PERCENT
:
285 const bool bLogScale
= lclIsLogarithmicScale( mrModel
);
287 aScaleData
.Scaling
= LogarithmicScaling::create( comphelper::getProcessComponentContext() );
289 aScaleData
.Scaling
= LinearScaling::create( comphelper::getProcessComponentContext() );
291 lclSetValueOrClearAny( aScaleData
.Minimum
, mrModel
.mofMin
);
292 lclSetValueOrClearAny( aScaleData
.Maximum
, mrModel
.mofMax
);
294 IncrementData
& rIncrementData
= aScaleData
.IncrementData
;
295 if( mrModel
.mofMajorUnit
.has() && aScaleData
.Scaling
.is() )
296 rIncrementData
.Distance
<<= aScaleData
.Scaling
->doScaling( mrModel
.mofMajorUnit
.get() );
298 lclSetValueOrClearAny( rIncrementData
.Distance
, mrModel
.mofMajorUnit
);
300 Sequence
< SubIncrement
>& rSubIncrementSeq
= rIncrementData
.SubIncrements
;
301 rSubIncrementSeq
.realloc( 1 );
302 Any
& rIntervalCount
= rSubIncrementSeq
[ 0 ].IntervalCount
;
303 rIntervalCount
.clear();
306 if( mrModel
.mofMinorUnit
.has() )
307 rIntervalCount
<<= sal_Int32( 9 );
309 else if( mrModel
.mofMajorUnit
.has() && mrModel
.mofMinorUnit
.has() && (0.0 < mrModel
.mofMinorUnit
.get()) && (mrModel
.mofMinorUnit
.get() <= mrModel
.mofMajorUnit
.get()) )
311 double fCount
= mrModel
.mofMajorUnit
.get() / mrModel
.mofMinorUnit
.get() + 0.5;
312 if( (1.0 <= fCount
) && (fCount
< 1001.0) )
313 rIntervalCount
<<= static_cast< sal_Int32
>( fCount
);
315 else if( !mrModel
.mofMinorUnit
.has() )
317 // tdf#114168 If minor unit is not set then set interval to 5, as MS Excel do.
318 rIntervalCount
<<= static_cast< sal_Int32
>( 5 );
323 OSL_FAIL( "AxisConverter::convertFromModel - unknown axis type" );
326 /* Do not set a value to the Origin member anymore (already done via
327 new axis properties 'CrossoverPosition' and 'CrossoverValue'). */
328 aScaleData
.Origin
.clear();
330 // axis orientation ---------------------------------------------------
332 // #i85167# pie/donut charts need opposite direction at Y axis
333 // #i87747# radar charts need opposite direction at X axis
334 bool bMirrorDirection
=
335 ((nAxisIdx
== API_Y_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
)) ||
336 ((nAxisIdx
== API_X_AXIS
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_RADAR
));
337 bool bReverse
= (mrModel
.mnOrientation
== XML_maxMin
) != bMirrorDirection
;
338 aScaleData
.Orientation
= bReverse
? cssc2::AxisOrientation_REVERSE
: cssc2::AxisOrientation_MATHEMATICAL
;
340 // write back scaling data
341 xAxis
->setScaleData( aScaleData
);
343 // number format ------------------------------------------------------
345 if( (aScaleData
.AxisType
== cssc2::AxisType::REALNUMBER
) || (aScaleData
.AxisType
== cssc2::AxisType::PERCENT
) )
346 getFormatter().convertNumberFormat(aAxisProp
, mrModel
.maNumberFormat
, true);
348 // position of crossing axis ------------------------------------------
350 bool bManualCrossing
= mrModel
.mofCrossesAt
.has();
351 cssc::ChartAxisPosition eAxisPos
= cssc::ChartAxisPosition_VALUE
;
352 if( !bManualCrossing
) switch( mrModel
.mnCrossMode
)
354 case XML_min
: eAxisPos
= cssc::ChartAxisPosition_START
; break;
355 case XML_max
: eAxisPos
= cssc::ChartAxisPosition_END
; break;
356 case XML_autoZero
: eAxisPos
= cssc::ChartAxisPosition_ZERO
; break;
358 if( !mrModel
.mbAuto
)
359 aAxisProp
.setProperty( PROP_CrossoverPosition
, eAxisPos
);
361 // calculate automatic origin depending on scaling mode of crossing axis
362 bool bCrossingLogScale
= pCrossingAxis
&& lclIsLogarithmicScale( *pCrossingAxis
);
363 double fCrossingPos
= bManualCrossing
? mrModel
.mofCrossesAt
.get() : (bCrossingLogScale
? 1.0 : 0.0);
364 aAxisProp
.setProperty( PROP_CrossoverValue
, fCrossingPos
);
366 // axis title ---------------------------------------------------------
368 // in radar charts, title objects may exist, but are not shown
369 if( mrModel
.mxTitle
.is() && (rTypeGroups
.front()->getTypeInfo().meTypeCategory
!= TYPECATEGORY_RADAR
) )
371 Reference
< XTitled
> xTitled( xAxis
, UNO_QUERY_THROW
);
372 TitleConverter
aTitleConv( *this, *mrModel
.mxTitle
);
373 aTitleConv
.convertFromModel( xTitled
, OoxResId(STR_DIAGRAM_AXISTITLE
), OBJECTTYPE_AXISTITLE
, nAxesSetIdx
, nAxisIdx
);
376 // axis data unit label -----------------------------------------------
377 AxisDispUnitsConverter
axisDispUnitsConverter (*this, mrModel
.mxDispUnits
.getOrCreate());
378 axisDispUnitsConverter
.convertFromModel(xAxis
);
384 if( xAxis
.is() && rxCoordSystem
.is() ) try
386 // insert axis into coordinate system
387 rxCoordSystem
->setAxisByDimension( nAxisIdx
, xAxis
, nAxesSetIdx
);
391 OSL_FAIL( "AxisConverter::convertFromModel - cannot insert axis into coordinate system" );
395 AxisDispUnitsConverter::AxisDispUnitsConverter( const ConverterRoot
& rParent
, AxisDispUnitsModel
& rModel
) :
396 ConverterBase
< AxisDispUnitsModel
>( rParent
, rModel
)
400 AxisDispUnitsConverter::~AxisDispUnitsConverter()
404 void AxisDispUnitsConverter::convertFromModel( const Reference
< XAxis
>& rxAxis
)
406 PropertySet
aPropSet( rxAxis
);
407 if (!mrModel
.mnBuiltInUnit
.isEmpty() )
409 aPropSet
.setProperty(PROP_DisplayUnits
, true);
410 aPropSet
.setProperty( PROP_BuiltInUnit
, mrModel
.mnBuiltInUnit
);
415 } // namespace drawingml
418 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */