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