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