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
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 {
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)
isDataCompatible(const InterpretedData & aInterpretedData)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
reinterpretDataSeries(const InterpretedData & aInterpretedData)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