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