Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include "drawingml/chart/plotareaconverter.hxx"
21 :
22 : #include <com/sun/star/chart/XChartDocument.hpp>
23 : #include <com/sun/star/chart/XDiagramPositioning.hpp>
24 : #include <com/sun/star/chart2/XChartDocument.hpp>
25 : #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
26 : #include <com/sun/star/chart2/XDiagram.hpp>
27 : #include <com/sun/star/drawing/Direction3D.hpp>
28 : #include <com/sun/star/drawing/ProjectionMode.hpp>
29 : #include <com/sun/star/drawing/ShadeMode.hpp>
30 : #include "drawingml/chart/axisconverter.hxx"
31 : #include "drawingml/chart/plotareamodel.hxx"
32 : #include "drawingml/chart/typegroupconverter.hxx"
33 :
34 : namespace oox {
35 : namespace drawingml {
36 : namespace chart {
37 :
38 : using namespace ::com::sun::star;
39 : using namespace ::com::sun::star::chart2;
40 : using namespace ::com::sun::star::uno;
41 :
42 : namespace {
43 :
44 : /** Axes set model. This is a helper for the plot area converter collecting all
45 : type groups and axes of the primary or secondary axes set. */
46 : struct AxesSetModel
47 : {
48 : typedef ModelVector< TypeGroupModel > TypeGroupVector;
49 : typedef ModelMap< sal_Int32, AxisModel > AxisMap;
50 :
51 : TypeGroupVector maTypeGroups; /// All type groups containing data series.
52 : AxisMap maAxes; /// All axes mapped by API axis type.
53 :
54 238 : inline explicit AxesSetModel() {}
55 238 : inline ~AxesSetModel() {}
56 : };
57 :
58 : /** Axes set converter. This is a helper class for the plot area converter. */
59 : class AxesSetConverter : public ConverterBase< AxesSetModel >
60 : {
61 : public:
62 : explicit AxesSetConverter( const ConverterRoot& rParent, AxesSetModel& rModel );
63 : virtual ~AxesSetConverter();
64 :
65 : /** Converts the axes set model to a chart2 diagram. Returns an automatic
66 : chart title from a single series title, if possible. */
67 : void convertFromModel(
68 : const Reference< XDiagram >& rxDiagram,
69 : View3DModel& rView3DModel,
70 : sal_Int32 nAxesSetIdx,
71 : bool bSupportsVaryColorsByPoint );
72 :
73 : /** Returns the automatic chart title if the axes set contains only one series. */
74 224 : inline const OUString& getAutomaticTitle() const { return maAutoTitle; }
75 : /** Returns true, if the chart is three-dimensional. */
76 224 : inline bool is3dChart() const { return mb3dChart; }
77 : /** Returns true, if chart type supports wall and floor format in 3D mode. */
78 224 : inline bool isWall3dChart() const { return mbWall3dChart; }
79 : /** Returns true, if chart is a pie chart or doughnut chart. */
80 224 : inline bool isPieChart() const { return mbPieChart; }
81 :
82 : private:
83 : OUString maAutoTitle;
84 : bool mb3dChart;
85 : bool mbWall3dChart;
86 : bool mbPieChart;
87 : };
88 :
89 238 : AxesSetConverter::AxesSetConverter( const ConverterRoot& rParent, AxesSetModel& rModel ) :
90 : ConverterBase< AxesSetModel >( rParent, rModel ),
91 : mb3dChart( false ),
92 : mbWall3dChart( false ),
93 238 : mbPieChart( false )
94 : {
95 238 : }
96 :
97 238 : AxesSetConverter::~AxesSetConverter()
98 : {
99 238 : }
100 :
101 476 : ModelRef< AxisModel > lclGetOrCreateAxis( const AxesSetModel::AxisMap& rFromAxes, sal_Int32 nAxisIdx, sal_Int32 nDefTypeId )
102 : {
103 476 : ModelRef< AxisModel > xAxis = rFromAxes.get( nAxisIdx );
104 476 : if( !xAxis )
105 84 : xAxis.create( nDefTypeId ).mbDeleted = true; // missing axis is invisible
106 476 : return xAxis;
107 : }
108 :
109 238 : void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram,
110 : View3DModel& rView3DModel, sal_Int32 nAxesSetIdx, bool bSupportsVaryColorsByPoint )
111 : {
112 : // create type group converter objects for all type groups
113 : typedef RefVector< TypeGroupConverter > TypeGroupConvVector;
114 238 : TypeGroupConvVector aTypeGroups;
115 480 : for( AxesSetModel::TypeGroupVector::iterator aIt = mrModel.maTypeGroups.begin(), aEnd = mrModel.maTypeGroups.end(); aIt != aEnd; ++aIt )
116 242 : aTypeGroups.push_back( TypeGroupConvVector::value_type( new TypeGroupConverter( *this, **aIt ) ) );
117 :
118 : OSL_ENSURE( !aTypeGroups.empty(), "AxesSetConverter::convertFromModel - no type groups in axes set" );
119 238 : if( !aTypeGroups.empty() ) try
120 : {
121 : // first type group needed for coordinate system and axis conversion
122 238 : TypeGroupConverter& rFirstTypeGroup = *aTypeGroups.front();
123 :
124 : // get automatic chart title, if there is only one type group
125 238 : if( aTypeGroups.size() == 1 )
126 234 : maAutoTitle = rFirstTypeGroup.getSingleSeriesTitle();
127 :
128 : /* Create a coordinate system. For now, all type groups from all axes sets
129 : have to be inserted into one coordinate system. Later, chart2 should
130 : support using one coordinate system for each axes set. */
131 238 : Reference< XCoordinateSystem > xCoordSystem;
132 476 : Reference< XCoordinateSystemContainer > xCoordSystemCont( rxDiagram, UNO_QUERY_THROW );
133 476 : Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems();
134 238 : if( aCoordSystems.hasElements() )
135 : {
136 : OSL_ENSURE( aCoordSystems.getLength() == 1, "AxesSetConverter::convertFromModel - too many coordinate systems" );
137 14 : xCoordSystem = aCoordSystems[ 0 ];
138 : OSL_ENSURE( xCoordSystem.is(), "AxesSetConverter::convertFromModel - invalid coordinate system" );
139 : }
140 : else
141 : {
142 224 : xCoordSystem = rFirstTypeGroup.createCoordinateSystem();
143 224 : if( xCoordSystem.is() )
144 224 : xCoordSystemCont->addCoordinateSystem( xCoordSystem );
145 : }
146 :
147 : // 3D view settings
148 238 : mb3dChart = rFirstTypeGroup.is3dChart();
149 238 : mbWall3dChart = rFirstTypeGroup.isWall3dChart();
150 238 : mbPieChart = rFirstTypeGroup.getTypeInfo().meTypeCategory == TYPECATEGORY_PIE;
151 238 : if( mb3dChart )
152 : {
153 70 : View3DConverter aView3DConv( *this, rView3DModel );
154 70 : aView3DConv.convertFromModel( rxDiagram, rFirstTypeGroup );
155 : }
156 :
157 : /* Convert all chart type groups. Each type group will add its series
158 : to the data provider attached to the chart document. */
159 238 : if( xCoordSystem.is() )
160 : {
161 : // convert all axes (create missing axis models)
162 238 : ModelRef< AxisModel > xXAxis = lclGetOrCreateAxis( mrModel.maAxes, API_X_AXIS, rFirstTypeGroup.getTypeInfo().mbCategoryAxis ? C_TOKEN( catAx ) : C_TOKEN( valAx ) );
163 476 : ModelRef< AxisModel > xYAxis = lclGetOrCreateAxis( mrModel.maAxes, API_Y_AXIS, C_TOKEN( valAx ) );
164 :
165 476 : AxisConverter aXAxisConv( *this, *xXAxis );
166 238 : aXAxisConv.convertFromModel( xCoordSystem, aTypeGroups, xYAxis.get(), nAxesSetIdx, API_X_AXIS );
167 476 : AxisConverter aYAxisConv( *this, *xYAxis );
168 238 : aYAxisConv.convertFromModel( xCoordSystem, aTypeGroups, xXAxis.get(), nAxesSetIdx, API_Y_AXIS );
169 :
170 238 : if( rFirstTypeGroup.isDeep3dChart() )
171 : {
172 0 : ModelRef< AxisModel > xZAxis = lclGetOrCreateAxis( mrModel.maAxes, API_Z_AXIS, C_TOKEN( serAx ) );
173 0 : AxisConverter aZAxisConv( *this, *xZAxis );
174 0 : aZAxisConv.convertFromModel( xCoordSystem, aTypeGroups, 0, nAxesSetIdx, API_Z_AXIS );
175 : }
176 :
177 : // convert all chart type groups, this converts all series data and formatting
178 480 : for( TypeGroupConvVector::iterator aTIt = aTypeGroups.begin(), aTEnd = aTypeGroups.end(); aTIt != aTEnd; ++aTIt )
179 480 : (*aTIt)->convertFromModel( rxDiagram, xCoordSystem, nAxesSetIdx, bSupportsVaryColorsByPoint );
180 238 : }
181 : }
182 0 : catch( Exception& )
183 : {
184 238 : }
185 238 : }
186 :
187 : } // namespace
188 :
189 70 : View3DConverter::View3DConverter( const ConverterRoot& rParent, View3DModel& rModel ) :
190 70 : ConverterBase< View3DModel >( rParent, rModel )
191 : {
192 70 : }
193 :
194 70 : View3DConverter::~View3DConverter()
195 : {
196 70 : }
197 :
198 70 : void View3DConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, TypeGroupConverter& rTypeGroup )
199 : {
200 : namespace cssd = ::com::sun::star::drawing;
201 70 : PropertySet aPropSet( rxDiagram );
202 :
203 70 : sal_Int32 nRotationY = 0;
204 70 : sal_Int32 nRotationX = 0;
205 70 : bool bRightAngled = false;
206 70 : sal_Int32 nAmbientColor = 0;
207 70 : sal_Int32 nLightColor = 0;
208 :
209 70 : if( rTypeGroup.getTypeInfo().meTypeCategory == TYPECATEGORY_PIE )
210 : {
211 : // Y rotation used as 'first pie slice angle' in 3D pie charts
212 12 : rTypeGroup.convertPieRotation( aPropSet, mrModel.monRotationY.get( 0 ) );
213 : // X rotation a.k.a. elevation (map OOXML [0..90] to Chart2 [-90,0])
214 12 : nRotationX = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.monRotationX.get( 15 ), 0, 90 ) - 90;
215 : // no right-angled axes in pie charts
216 12 : bRightAngled = false;
217 : // ambient color (Gray 30%)
218 12 : nAmbientColor = 0xB3B3B3;
219 : // light color (Gray 70%)
220 12 : nLightColor = 0x4C4C4C;
221 : }
222 : else // 3D bar/area/line charts
223 : {
224 : // Y rotation (OOXML [0..359], Chart2 [-179,180])
225 58 : nRotationY = mrModel.monRotationY.get( 20 );
226 : // X rotation a.k.a. elevation (OOXML [-90..90], Chart2 [-179,180])
227 58 : nRotationX = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.monRotationX.get( 15 ), -90, 90 );
228 : // right-angled axes
229 58 : bRightAngled = mrModel.mbRightAngled;
230 : // ambient color (Gray 20%)
231 58 : nAmbientColor = 0xCCCCCC;
232 : // light color (Gray 60%)
233 58 : nLightColor = 0x666666;
234 : }
235 :
236 : // Y rotation (map OOXML [0..359] to Chart2 [-179,180])
237 70 : nRotationY %= 360;
238 70 : if( nRotationY > 180 ) nRotationY -= 360;
239 : /* Perspective (map OOXML [0..200] to Chart2 [0,100]). Seems that MSO 2007 is
240 : buggy here, the XML plugin of MSO 2003 writes the correct perspective in
241 : the range from 0 to 100. We will emulate the wrong behaviour of MSO 2007. */
242 70 : sal_Int32 nPerspective = getLimitedValue< sal_Int32, sal_Int32 >( mrModel.mnPerspective / 2, 0, 100 );
243 : // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%)
244 70 : bool bParallel = bRightAngled || (nPerspective == 0);
245 70 : cssd::ProjectionMode eProjMode = bParallel ? cssd::ProjectionMode_PARALLEL : cssd::ProjectionMode_PERSPECTIVE;
246 :
247 : // set rotation properties
248 70 : aPropSet.setProperty( PROP_RightAngledAxes, bRightAngled );
249 70 : aPropSet.setProperty( PROP_RotationVertical, nRotationY );
250 70 : aPropSet.setProperty( PROP_RotationHorizontal, nRotationX );
251 70 : aPropSet.setProperty( PROP_Perspective, nPerspective );
252 70 : aPropSet.setProperty( PROP_D3DScenePerspective, eProjMode );
253 :
254 : // set light settings
255 70 : aPropSet.setProperty( PROP_D3DSceneShadeMode, cssd::ShadeMode_FLAT );
256 70 : aPropSet.setProperty( PROP_D3DSceneAmbientColor, nAmbientColor );
257 70 : aPropSet.setProperty( PROP_D3DSceneLightOn1, false );
258 70 : aPropSet.setProperty( PROP_D3DSceneLightOn2, true );
259 70 : aPropSet.setProperty( PROP_D3DSceneLightColor2, nLightColor );
260 70 : aPropSet.setProperty( PROP_D3DSceneLightDirection2, cssd::Direction3D( 0.2, 0.4, 1.0 ) );
261 70 : }
262 :
263 116 : WallFloorConverter::WallFloorConverter( const ConverterRoot& rParent, WallFloorModel& rModel ) :
264 116 : ConverterBase< WallFloorModel >( rParent, rModel )
265 : {
266 116 : }
267 :
268 116 : WallFloorConverter::~WallFloorConverter()
269 : {
270 116 : }
271 :
272 116 : void WallFloorConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, ObjectType eObjType )
273 : {
274 116 : if( rxDiagram.is() )
275 : {
276 116 : PropertySet aPropSet;
277 116 : switch( eObjType )
278 : {
279 58 : case OBJECTTYPE_FLOOR: aPropSet.set( rxDiagram->getFloor() ); break;
280 58 : case OBJECTTYPE_WALL: aPropSet.set( rxDiagram->getWall() ); break;
281 : default: OSL_FAIL( "WallFloorConverter::convertFromModel - invalid object type" );
282 : }
283 116 : if( aPropSet.is() )
284 116 : getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(), eObjType );
285 : }
286 116 : }
287 :
288 224 : DataTableConverter::DataTableConverter( const ConverterRoot& rParent, DataTableModel& rModel ) :
289 224 : ConverterBase< DataTableModel >( rParent, rModel )
290 : {
291 224 : }
292 :
293 224 : DataTableConverter::~DataTableConverter()
294 : {
295 224 : }
296 :
297 224 : void DataTableConverter::convertFromModel( const Reference< XDiagram >& rxDiagram )
298 : {
299 224 : PropertySet aPropSet( rxDiagram );
300 224 : if (mrModel.mbShowHBorder)
301 28 : aPropSet.setProperty( PROP_DataTableHBorder, mrModel.mbShowHBorder );
302 224 : if (mrModel.mbShowVBorder)
303 28 : aPropSet.setProperty( PROP_DataTableVBorder, mrModel.mbShowVBorder);
304 224 : if (mrModel.mbShowOutline)
305 28 : aPropSet.setProperty( PROP_DataTableOutline, mrModel.mbShowOutline );
306 224 : }
307 :
308 224 : PlotAreaConverter::PlotAreaConverter( const ConverterRoot& rParent, PlotAreaModel& rModel ) :
309 : ConverterBase< PlotAreaModel >( rParent, rModel ),
310 : mb3dChart( false ),
311 : mbWall3dChart( false ),
312 224 : mbPieChart( false )
313 : {
314 224 : }
315 :
316 224 : PlotAreaConverter::~PlotAreaConverter()
317 : {
318 224 : }
319 :
320 224 : 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 224 : Reference< XDiagram > xDiagram;
325 : try
326 : {
327 224 : xDiagram.set( createInstance( "com.sun.star.chart2.Diagram" ), UNO_QUERY_THROW );
328 224 : getChartDocument()->setFirstDiagram( xDiagram );
329 : }
330 0 : 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 448 : AxisMap aAxisMap;
337 616 : for( PlotAreaModel::AxisVector::iterator aAIt = mrModel.maAxes.begin(), aAEnd = mrModel.maAxes.end(); aAIt != aAEnd; ++aAIt )
338 : {
339 392 : 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 392 : if( xAxis->mnAxisId != -1 )
343 392 : aAxisMap[ xAxis->mnAxisId ] = xAxis;
344 392 : }
345 :
346 : // group the type group models into different axes sets
347 : typedef ModelVector< AxesSetModel > AxesSetVector;
348 448 : AxesSetVector aAxesSets;
349 224 : sal_Int32 nMaxSeriesIdx = -1;
350 466 : for( PlotAreaModel::TypeGroupVector::iterator aTIt = mrModel.maTypeGroups.begin(), aTEnd = mrModel.maTypeGroups.end(); aTIt != aTEnd; ++aTIt )
351 : {
352 242 : PlotAreaModel::TypeGroupVector::value_type xTypeGroup = *aTIt;
353 242 : if( !xTypeGroup->maSeries.empty() )
354 : {
355 : // try to find a compatible axes set for the type group
356 242 : AxesSetModel* pAxesSet = 0;
357 260 : for( AxesSetVector::iterator aASIt = aAxesSets.begin(), aASEnd = aAxesSets.end(); !pAxesSet && (aASIt != aASEnd); ++aASIt )
358 18 : if( (*aASIt)->maTypeGroups.front()->maAxisIds == xTypeGroup->maAxisIds )
359 4 : pAxesSet = aASIt->get();
360 :
361 : // not possible to insert into an existing axes set -> start a new axes set
362 242 : if( !pAxesSet )
363 : {
364 238 : pAxesSet = &aAxesSets.create();
365 : // find axis models used by the type group
366 238 : const TypeGroupModel::AxisIdVector& rAxisIds = xTypeGroup->maAxisIds;
367 238 : if( rAxisIds.size() >= 1 )
368 196 : pAxesSet->maAxes[ API_X_AXIS ] = aAxisMap.get( rAxisIds[ 0 ] );
369 238 : if( rAxisIds.size() >= 2 )
370 196 : pAxesSet->maAxes[ API_Y_AXIS ] = aAxisMap.get( rAxisIds[ 1 ] );
371 238 : if( rAxisIds.size() >= 3 )
372 58 : pAxesSet->maAxes[ API_Z_AXIS ] = aAxisMap.get( rAxisIds[ 2 ] );
373 : }
374 :
375 : // insert the type group model
376 242 : pAxesSet->maTypeGroups.push_back( xTypeGroup );
377 :
378 : // collect the maximum series index for automatic series formatting
379 740 : for( TypeGroupModel::SeriesVector::iterator aSIt = xTypeGroup->maSeries.begin(), aSEnd = xTypeGroup->maSeries.end(); aSIt != aSEnd; ++aSIt )
380 498 : nMaxSeriesIdx = ::std::max( nMaxSeriesIdx, (*aSIt)->mnIndex );
381 : }
382 242 : }
383 224 : getFormatter().setMaxSeriesIndex( nMaxSeriesIdx );
384 :
385 : // varying point colors only for single series in single chart type
386 224 : bool bSupportsVaryColorsByPoint = mrModel.maTypeGroups.size() == 1;
387 :
388 : // convert all axes sets
389 462 : for( AxesSetVector::iterator aASBeg = aAxesSets.begin(), aASIt = aASBeg, aASEnd = aAxesSets.end(); aASIt != aASEnd; ++aASIt )
390 : {
391 238 : AxesSetConverter aAxesSetConv( *this, **aASIt );
392 238 : sal_Int32 nAxesSetIdx = static_cast< sal_Int32 >( aASIt - aASBeg );
393 238 : aAxesSetConv.convertFromModel( xDiagram, rView3DModel, nAxesSetIdx, bSupportsVaryColorsByPoint );
394 238 : if( nAxesSetIdx == 0 )
395 : {
396 224 : maAutoTitle = aAxesSetConv.getAutomaticTitle();
397 224 : mb3dChart = aAxesSetConv.is3dChart();
398 224 : mbWall3dChart = aAxesSetConv.isWall3dChart();
399 224 : mbPieChart = aAxesSetConv.isPieChart();
400 : }
401 : else
402 : {
403 14 : maAutoTitle = OUString();
404 : }
405 238 : }
406 :
407 448 : DataTableConverter dataTableConverter (*this, mrModel.mxDataTable.getOrCreate());
408 224 : dataTableConverter.convertFromModel(xDiagram);
409 : // plot area formatting
410 224 : if( xDiagram.is() && !mb3dChart )
411 : {
412 154 : PropertySet aPropSet( xDiagram->getWall() );
413 154 : getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, OBJECTTYPE_PLOTAREA2D );
414 224 : }
415 224 : }
416 :
417 224 : void PlotAreaConverter::convertPositionFromModel()
418 : {
419 224 : LayoutModel& rLayout = mrModel.mxLayout.getOrCreate();
420 224 : LayoutConverter aLayoutConv( *this, rLayout );
421 224 : awt::Rectangle aDiagramRect;
422 224 : if( aLayoutConv.calcAbsRectangle( aDiagramRect ) ) try
423 : {
424 : namespace cssc = ::com::sun::star::chart;
425 2 : Reference< cssc::XChartDocument > xChart1Doc( getChartDocument(), UNO_QUERY_THROW );
426 4 : Reference< cssc::XDiagramPositioning > xPositioning( xChart1Doc->getDiagram(), UNO_QUERY_THROW );
427 : // for pie charts, always set inner plot area size to exclude the data labels as Excel does
428 2 : sal_Int32 nTarget = (mbPieChart && (rLayout.mnTarget == XML_outer)) ? XML_inner : rLayout.mnTarget;
429 2 : switch( nTarget )
430 : {
431 : case XML_inner:
432 2 : xPositioning->setDiagramPositionExcludingAxes( aDiagramRect );
433 2 : break;
434 : case XML_outer:
435 0 : xPositioning->setDiagramPositionIncludingAxes( aDiagramRect );
436 0 : break;
437 : default:
438 : OSL_FAIL( "PlotAreaConverter::convertPositionFromModel - unknown positioning target" );
439 2 : }
440 : }
441 0 : catch( Exception& )
442 : {
443 224 : }
444 224 : }
445 :
446 : } // namespace chart
447 : } // namespace drawingml
448 408 : } // namespace oox
449 :
450 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|