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