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 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_chart2.hxx"
30 
31 #include "StockDataInterpreter.hxx"
32 #include "DataSeries.hxx"
33 #include "macros.hxx"
34 #include "DataSeriesHelper.hxx"
35 #include "CommonConverters.hxx"
36 #include "ContainerHelper.hxx"
37 #include <com/sun/star/beans/XPropertySet.hpp>
38 #include <com/sun/star/chart2/data/XDataSink.hpp>
39 
40 // #include <deque>
41 
42 #include <vector>
43 #include <algorithm>
44 #include <iterator>
45 
46 using namespace ::com::sun::star;
47 using namespace ::com::sun::star::chart2;
48 using namespace ::std;
49 
50 using ::com::sun::star::uno::Reference;
51 using ::com::sun::star::uno::Sequence;
52 using ::rtl::OUString;
53 using namespace ::chart::ContainerHelper;
54 
55 namespace chart
56 {
57 
58 // explicit
59 StockDataInterpreter::StockDataInterpreter(
60     StockChartTypeTemplate::StockVariant eVariant,
61     const Reference< uno::XComponentContext > & xContext ) :
62         DataInterpreter( xContext ),
63         m_eStockVariant( eVariant )
64 {}
65 
66 StockDataInterpreter::~StockDataInterpreter()
67 {}
68 
69 StockChartTypeTemplate::StockVariant StockDataInterpreter::GetStockVariant() const
70 {
71     return m_eStockVariant;
72 }
73 
74 // ____ XDataInterpreter ____
75 InterpretedData SAL_CALL StockDataInterpreter::interpretDataSource(
76     const Reference< data::XDataSource >& xSource,
77     const Sequence< beans::PropertyValue >& rArguments,
78     const Sequence< Reference< XDataSeries > >& rSeriesToReUse )
79     throw (uno::RuntimeException)
80 {
81     if( ! xSource.is())
82         return InterpretedData();
83 
84     Reference< data::XLabeledDataSequence > xCategories;
85     Sequence< Reference< data::XLabeledDataSequence > > aData( xSource->getDataSequences() );
86     const sal_Int32 nDataCount( aData.getLength());
87 
88     // sub-type properties
89     const StockChartTypeTemplate::StockVariant eVar( GetStockVariant());
90     const bool bHasOpenValues (( eVar == StockChartTypeTemplate::OPEN_LOW_HI_CLOSE ) ||
91                                ( eVar == StockChartTypeTemplate::VOL_OPEN_LOW_HI_CLOSE ));
92     const bool bHasVolume (( eVar == StockChartTypeTemplate::VOL_LOW_HI_CLOSE ) ||
93                            ( eVar == StockChartTypeTemplate::VOL_OPEN_LOW_HI_CLOSE ));
94     const bool bHasCategories( HasCategories( rArguments, aData ));
95 
96     // necessary roles for "full series"
97     // low/high/close
98     sal_Int32 nNumberOfNecessarySequences( 3 );
99     if( bHasOpenValues )
100         ++nNumberOfNecessarySequences;
101     if( bHasVolume )
102         ++nNumberOfNecessarySequences;
103 
104     // calculate number of full series (nNumOfFullSeries) and the number of remaining
105     // sequences used for additional "incomplete series" (nRemaining)
106     sal_Int32 nNumOfFullSeries( 0 );
107     sal_Int32 nRemaining( 0 );
108     {
109         sal_Int32 nAvailableSequences( nDataCount );
110         if( bHasCategories )
111             --nAvailableSequences;
112         nNumOfFullSeries = nAvailableSequences / nNumberOfNecessarySequences;
113         nRemaining = nAvailableSequences % nNumberOfNecessarySequences;
114     }
115     sal_Int32 nCandleStickSeries = nNumOfFullSeries;
116     sal_Int32 nVolumeSeries = nNumOfFullSeries;
117 
118     sal_Int32 nNumberOfGroups( bHasVolume ? 2 : 1 );
119     // sequences of data::XLabeledDataSequence per series per group
120     Sequence< Sequence< Sequence< Reference< data::XLabeledDataSequence > > > > aSequences( nNumberOfGroups );
121     sal_Int32 nBarGroupIndex( 0 );
122     sal_Int32 nCandleStickGroupIndex( nNumberOfGroups - 1 );
123 
124     // allocate space for labeled sequences
125     if( nRemaining > 0  )
126         ++nCandleStickSeries;
127     aSequences[nCandleStickGroupIndex].realloc( nCandleStickSeries );
128     if( bHasVolume )
129     {
130         // if there are remaining sequences, the first one is taken for
131         // additional close values, the second one is taken as volume, if volume
132         // is used
133         if( nRemaining > 1 )
134             ++nVolumeSeries;
135         aSequences[nBarGroupIndex].realloc( nVolumeSeries );
136     }
137 
138 
139     // create data
140     sal_Int32 nSourceIndex = 0;   // index into aData sequence
141 
142     // 1. categories
143     if( bHasCategories )
144     {
145         xCategories.set( aData[nSourceIndex] );
146         ++nSourceIndex;
147     }
148 
149     // 2. create "full" series
150     for( sal_Int32 nLabeledSeqIdx=0; nLabeledSeqIdx<nNumOfFullSeries; ++nLabeledSeqIdx )
151     {
152         // bar
153         if( bHasVolume )
154         {
155             aSequences[nBarGroupIndex][nLabeledSeqIdx].realloc( 1 );
156             aSequences[nBarGroupIndex][nLabeledSeqIdx][0].set( aData[nSourceIndex] );
157             if( aData[nSourceIndex].is())
158                 SetRole( aData[nSourceIndex]->getValues(), C2U("values-y"));
159             ++nSourceIndex;
160         }
161 
162         sal_Int32 nSeqIdx = 0;
163         if( bHasOpenValues )
164         {
165             aSequences[nCandleStickGroupIndex][nLabeledSeqIdx].realloc( 4 );
166             aSequences[nCandleStickGroupIndex][nLabeledSeqIdx][nSeqIdx].set( aData[nSourceIndex] );
167             if( aData[nSourceIndex].is())
168                 SetRole( aData[nSourceIndex]->getValues(), C2U("values-first"));
169             ++nSourceIndex, ++nSeqIdx;
170         }
171         else
172             aSequences[nCandleStickGroupIndex][nLabeledSeqIdx].realloc( 3 );
173 
174         aSequences[nCandleStickGroupIndex][nLabeledSeqIdx][nSeqIdx].set( aData[nSourceIndex] );
175         if( aData[nSourceIndex].is())
176             SetRole( aData[nSourceIndex]->getValues(), C2U("values-min"));
177         ++nSourceIndex, ++nSeqIdx;
178 
179         aSequences[nCandleStickGroupIndex][nLabeledSeqIdx][nSeqIdx].set( aData[nSourceIndex] );
180         if( aData[nSourceIndex].is())
181             SetRole( aData[nSourceIndex]->getValues(), C2U("values-max"));
182         ++nSourceIndex, ++nSeqIdx;
183 
184         aSequences[nCandleStickGroupIndex][nLabeledSeqIdx][nSeqIdx].set( aData[nSourceIndex] );
185         if( aData[nSourceIndex].is())
186             SetRole( aData[nSourceIndex]->getValues(), C2U("values-last"));
187         ++nSourceIndex, ++nSeqIdx;
188     }
189 
190     // 3. create series with remaining sequences
191     if( bHasVolume && nRemaining > 1 )
192     {
193         OSL_ASSERT( nVolumeSeries > nNumOfFullSeries );
194         aSequences[nBarGroupIndex][nVolumeSeries - 1].realloc( 1 );
195         OSL_ASSERT( nDataCount > nSourceIndex );
196         if( aData[nSourceIndex].is())
197             SetRole( aData[nSourceIndex]->getValues(), C2U("values-y"));
198         aSequences[nBarGroupIndex][nVolumeSeries - 1][0].set( aData[nSourceIndex] );
199         ++nSourceIndex;
200         --nRemaining;
201         OSL_ENSURE( nRemaining, "additional bar should only be used if there is at least one more sequence for a candle stick" );
202     }
203 
204     // candle-stick
205     if( nRemaining > 0 )
206     {
207         OSL_ASSERT( nCandleStickSeries > nNumOfFullSeries );
208         const sal_Int32 nSeriesIndex = nCandleStickSeries - 1;
209         aSequences[nCandleStickGroupIndex][nSeriesIndex].realloc( nRemaining );
210         OSL_ASSERT( nDataCount > nSourceIndex );
211 
212         // 1. low
213         sal_Int32 nSeqIdx( 0 );
214         aSequences[nCandleStickGroupIndex][nSeriesIndex][nSeqIdx].set( aData[nSourceIndex] );
215         if( aData[nSourceIndex].is())
216             SetRole( aData[nSourceIndex]->getValues(), C2U("values-min"));
217         ++nSourceIndex, ++nSeqIdx;
218 
219         // 2. high
220         if( nSeqIdx < nRemaining )
221         {
222             aSequences[nCandleStickGroupIndex][nSeriesIndex][nSeqIdx].set( aData[nSourceIndex] );
223             if( aData[nSourceIndex].is())
224                 SetRole( aData[nSourceIndex]->getValues(), C2U("values-max"));
225             ++nSourceIndex, ++nSeqIdx;
226         }
227 
228         // 3. close
229         OSL_ENSURE( bHasOpenValues || nSeqIdx >= nRemaining, "could have created full series" );
230         if( nSeqIdx < nRemaining )
231         {
232             aSequences[nCandleStickGroupIndex][nSeriesIndex][nSeqIdx].set( aData[nSourceIndex] );
233             if( aData[nSourceIndex].is())
234                 SetRole( aData[nSourceIndex]->getValues(), C2U("values-last"));
235             ++nSourceIndex, ++nSeqIdx;
236         }
237 
238         // 4. open
239         OSL_ENSURE( nSeqIdx >= nRemaining, "could have created full series" );
240     }
241 
242     // create DataSeries
243     Sequence< Sequence< Reference< XDataSeries > > > aResultSeries( nNumberOfGroups );
244     sal_Int32 nGroupIndex, nReUsedSeriesIdx = 0;
245     for( nGroupIndex=0; nGroupIndex<nNumberOfGroups; ++nGroupIndex )
246     {
247         const sal_Int32 nNumSeriesData = aSequences[nGroupIndex].getLength();
248         aResultSeries[nGroupIndex].realloc( nNumSeriesData );
249         for( sal_Int32 nSeriesIdx = 0; nSeriesIdx < nNumSeriesData; ++nSeriesIdx, ++nReUsedSeriesIdx )
250         {
251             try
252             {
253                 Reference< XDataSeries > xSeries;
254                 if( nReUsedSeriesIdx < rSeriesToReUse.getLength())
255                     xSeries.set( rSeriesToReUse[nReUsedSeriesIdx] );
256                 else
257                     xSeries.set( new DataSeries( GetComponentContext() ) );
258                 OSL_ASSERT( xSeries.is() );
259                 Reference< data::XDataSink > xSink( xSeries, uno::UNO_QUERY_THROW );
260                 OSL_ASSERT( xSink.is() );
261                 xSink->setData( aSequences[nGroupIndex][nSeriesIdx] );
262                 aResultSeries[nGroupIndex][nSeriesIdx].set( xSeries );
263             }
264             catch( uno::Exception & ex )
265             {
266                 ASSERT_EXCEPTION( ex );
267             }
268         }
269     }
270 
271     return InterpretedData( aResultSeries, xCategories );
272 }
273 
274 // criterion: there must be two groups for stock-charts with volume and all
275 // series must have the correct number of data::XLabeledDataSequences
276 
277 // todo: skip first criterion? (to allow easy switch from stock-chart without
278 // volume to one with volume)
279 sal_Bool SAL_CALL StockDataInterpreter::isDataCompatible(
280     const InterpretedData& aInterpretedData )
281     throw (uno::RuntimeException)
282 {
283     // high/low/close
284     sal_Int32 nNumberOfNecessarySequences = 3;
285     // open
286     StockChartTypeTemplate::StockVariant eVar( GetStockVariant());
287     if( ( eVar == StockChartTypeTemplate::OPEN_LOW_HI_CLOSE ) ||
288         ( eVar == StockChartTypeTemplate::VOL_OPEN_LOW_HI_CLOSE ))
289         ++nNumberOfNecessarySequences;
290     // volume
291     bool bHasVolume = (( eVar == StockChartTypeTemplate::VOL_LOW_HI_CLOSE ) ||
292                        ( eVar == StockChartTypeTemplate::VOL_OPEN_LOW_HI_CLOSE ));
293 
294     // 1. correct number of sub-types
295     if( aInterpretedData.Series.getLength() < (bHasVolume ? 2 : 1 ))
296         return sal_False;
297 
298     // 2. a. volume -- use default check
299     if( bHasVolume )
300     {
301         if( ! DataInterpreter::isDataCompatible(
302                 InterpretedData( Sequence< Sequence< Reference< XDataSeries > > >(
303                                      aInterpretedData.Series.getConstArray(), 1 ),
304                                  aInterpretedData.Categories )))
305             return sal_False;
306     }
307 
308     // 2. b. candlestick
309     {
310         OSL_ASSERT( aInterpretedData.Series.getLength() > (bHasVolume ? 1 : 0));
311         Sequence< Reference< XDataSeries > > aSeries( aInterpretedData.Series[(bHasVolume ? 1 : 0)] );
312         if(!aSeries.getLength())
313             return sal_False;
314         for( sal_Int32 i=0; i<aSeries.getLength(); ++i )
315         {
316             try
317             {
318                 Reference< data::XDataSource > xSrc( aSeries[i], uno::UNO_QUERY_THROW );
319                 Sequence< Reference< data::XLabeledDataSequence > > aSeq( xSrc->getDataSequences());
320                 if( aSeq.getLength() != nNumberOfNecessarySequences )
321                     return sal_False;
322             }
323             catch( uno::Exception & ex )
324             {
325                 ASSERT_EXCEPTION( ex );
326             }
327         }
328     }
329 
330     // 2. c. additional series
331     // ignore
332 
333     return sal_True;
334 }
335 
336 InterpretedData SAL_CALL StockDataInterpreter::reinterpretDataSeries(
337     const InterpretedData& aInterpretedData )
338     throw (uno::RuntimeException)
339 {
340     // prerequisite: StockDataInterpreter::isDataCompatible() returned true
341     return aInterpretedData;
342 }
343 
344 } // namespace chart
345