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 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 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) 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 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