LibreOffice Module chart2 (master) 1
StockDataInterpreter.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21
22#include <cstddef>
23
26#include <DataSeries.hxx>
28
29using namespace ::com::sun::star;
30using namespace ::com::sun::star::chart2;
31
32using ::com::sun::star::uno::Reference;
33using ::com::sun::star::uno::Sequence;
34
35namespace chart
36{
37
38// explicit
41 m_eStockVariant( eVariant )
42{}
43
45{}
46
47// ____ XDataInterpreter ____
49 const Reference< data::XDataSource >& xSource,
50 const Sequence< beans::PropertyValue >& rArguments,
51 const std::vector< rtl::Reference< ::chart::DataSeries > >& rSeriesToReUse )
52{
53 if( ! xSource.is())
54 return InterpretedData();
55
57 std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aData = DataInterpreter::getDataSequences(xSource);
58 const sal_Int32 nDataCount( aData.size());
59
60 // sub-type properties
62 const bool bHasOpenValues (( eVar == StockChartTypeTemplate::StockVariant::Open ) ||
64 const bool bHasVolume (( eVar == StockChartTypeTemplate::StockVariant::Volume ) ||
66 const bool bHasCategories( HasCategories( rArguments, aData ));
67
68 // necessary roles for "full series"
69 // low/high/close
70 sal_Int32 nNumberOfNecessarySequences( 3 );
71 if( bHasOpenValues )
72 ++nNumberOfNecessarySequences;
73 if( bHasVolume )
74 ++nNumberOfNecessarySequences;
75
76 // calculate number of full series (nNumOfFullSeries) and the number of remaining
77 // sequences used for additional "incomplete series" (nRemaining)
78 sal_Int32 nNumOfFullSeries( 0 );
79 sal_Int32 nRemaining( 0 );
80 {
81 sal_Int32 nAvailableSequences( nDataCount );
82 if( bHasCategories )
83 --nAvailableSequences;
84 nNumOfFullSeries = nAvailableSequences / nNumberOfNecessarySequences;
85 nRemaining = nAvailableSequences % nNumberOfNecessarySequences;
86 }
87 sal_Int32 nCandleStickSeries = nNumOfFullSeries;
88 sal_Int32 nVolumeSeries = nNumOfFullSeries;
89
90 sal_Int32 nNumberOfGroups( bHasVolume ? 2 : 1 );
91 // sequences of data::XLabeledDataSequence per series per group
92 std::vector< std::vector< std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > > > aSequences( nNumberOfGroups );
93 const sal_Int32 nBarGroupIndex( 0 );
94 const sal_Int32 nCandleStickGroupIndex( nNumberOfGroups - 1 );
95
96 // allocate space for labeled sequences
97 if( nRemaining > 0 )
98 ++nCandleStickSeries;
99 aSequences[nCandleStickGroupIndex].resize( nCandleStickSeries );
100 auto & pCandleStickGroup = aSequences[nCandleStickGroupIndex];
101 if( bHasVolume )
102 {
103 // if there are remaining sequences, the first one is taken for
104 // additional close values, the second one is taken as volume, if volume
105 // is used
106 if( nRemaining > 1 )
107 ++nVolumeSeries;
108 aSequences[nBarGroupIndex].resize( nVolumeSeries );
109 }
110 auto & pBarGroup = aSequences[nBarGroupIndex];
111
112 // create data
113 sal_Int32 nSourceIndex = 0; // index into aData sequence
114
115 // 1. categories
116 if( bHasCategories )
117 {
118 xCategories = aData[nSourceIndex];
119 ++nSourceIndex;
120 }
121
122 // 2. create "full" series
123 for( sal_Int32 nLabeledSeqIdx=0; nLabeledSeqIdx<nNumOfFullSeries; ++nLabeledSeqIdx )
124 {
125 // bar
126 if( bHasVolume )
127 {
128 pBarGroup[nLabeledSeqIdx].resize( 1 );
129 pBarGroup[nLabeledSeqIdx][0] = aData[nSourceIndex];
130 if( aData[nSourceIndex].is())
131 SetRole( aData[nSourceIndex]->getValues(), "values-y");
132 ++nSourceIndex;
133 }
134
135 sal_Int32 nSeqIdx = 0;
136 if( bHasOpenValues )
137 {
138 pCandleStickGroup[nLabeledSeqIdx].resize( 4 );
139 pCandleStickGroup[nLabeledSeqIdx][nSeqIdx] = aData[nSourceIndex];
140 if( aData[nSourceIndex].is())
141 SetRole( aData[nSourceIndex]->getValues(), "values-first");
142 ++nSourceIndex;
143 ++nSeqIdx;
144 }
145 else
146 pCandleStickGroup[nLabeledSeqIdx].resize( 3 );
147 auto & pLabeledSeq = pCandleStickGroup[nLabeledSeqIdx];
148
149 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
150 if( aData[nSourceIndex].is())
151 SetRole( aData[nSourceIndex]->getValues(), "values-min");
152 ++nSourceIndex;
153 ++nSeqIdx;
154
155 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
156 if( aData[nSourceIndex].is())
157 SetRole( aData[nSourceIndex]->getValues(), "values-max");
158 ++nSourceIndex;
159 ++nSeqIdx;
160
161 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
162 if( aData[nSourceIndex].is())
163 SetRole( aData[nSourceIndex]->getValues(), "values-last");
164 ++nSourceIndex;
165 ++nSeqIdx;
166 }
167
168 // 3. create series with remaining sequences
169 if( bHasVolume && nRemaining > 1 )
170 {
171 OSL_ASSERT( nVolumeSeries > nNumOfFullSeries );
172 pBarGroup[nVolumeSeries - 1].resize( 1 );
173 OSL_ASSERT( nDataCount > nSourceIndex );
174 if( aData[nSourceIndex].is())
175 SetRole( aData[nSourceIndex]->getValues(), "values-y");
176 pBarGroup[nVolumeSeries - 1][0] = aData[nSourceIndex];
177 ++nSourceIndex;
178 --nRemaining;
179 OSL_ENSURE( nRemaining, "additional bar should only be used if there is at least one more sequence for a candle stick" );
180 }
181
182 // candle-stick
183 if( nRemaining > 0 )
184 {
185 OSL_ASSERT( nCandleStickSeries > nNumOfFullSeries );
186 const sal_Int32 nSeriesIndex = nCandleStickSeries - 1;
187 pCandleStickGroup[nSeriesIndex].resize( nRemaining );
188 auto & pLabeledSeq = pCandleStickGroup[nSeriesIndex];
189 OSL_ASSERT( nDataCount > nSourceIndex );
190
191 // 1. low
192 sal_Int32 nSeqIdx( 0 );
193 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
194 if( aData[nSourceIndex].is())
195 SetRole( aData[nSourceIndex]->getValues(), "values-min");
196 ++nSourceIndex;
197 ++nSeqIdx;
198
199 // 2. high
200 if( nSeqIdx < nRemaining )
201 {
202 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
203 if( aData[nSourceIndex].is())
204 SetRole( aData[nSourceIndex]->getValues(), "values-max");
205 ++nSourceIndex;
206 ++nSeqIdx;
207 }
208
209 // 3. close
210 OSL_ENSURE( bHasOpenValues || nSeqIdx >= nRemaining, "could have created full series" );
211 if( nSeqIdx < nRemaining )
212 {
213 pLabeledSeq[nSeqIdx] = aData[nSourceIndex];
214 if( aData[nSourceIndex].is())
215 SetRole( aData[nSourceIndex]->getValues(), "values-last");
216 ++nSourceIndex;
217 ++nSeqIdx;
218 }
219
220 // 4. open
221 OSL_ENSURE( nSeqIdx >= nRemaining, "could have created full series" );
222 }
223
224 // create DataSeries
225 std::vector< std::vector< rtl::Reference< DataSeries > > > aResultSeries( nNumberOfGroups );
226 sal_Int32 nGroupIndex;
227 std::size_t nReUsedSeriesIdx = 0;
228 for( nGroupIndex=0; nGroupIndex<nNumberOfGroups; ++nGroupIndex )
229 {
230 const sal_Int32 nNumSeriesData = aSequences[nGroupIndex].size();
231 aResultSeries[nGroupIndex].resize( nNumSeriesData );
232 auto & pResultSerie = aResultSeries[nGroupIndex];
233 for( sal_Int32 nSeriesIdx = 0; nSeriesIdx < nNumSeriesData; ++nSeriesIdx, ++nReUsedSeriesIdx )
234 {
235 try
236 {
238 if( nReUsedSeriesIdx < rSeriesToReUse.size())
239 xSeries = rSeriesToReUse[nReUsedSeriesIdx];
240 else
241 xSeries = new DataSeries;
242 assert( xSeries.is() );
243 xSeries->setData( aSequences[nGroupIndex][nSeriesIdx] );
244 pResultSerie[nSeriesIdx] = xSeries;
245 }
246 catch( const uno::Exception & )
247 {
248 DBG_UNHANDLED_EXCEPTION("chart2");
249 }
250 }
251 }
252
253 return { aResultSeries, xCategories };
254}
255
256// criterion: there must be two groups for stock-charts with volume and all
257// series must have the correct number of data::XLabeledDataSequences
258
259// todo: skip first criterion? (to allow easy switch from stock-chart without
260// volume to one with volume)
262 const InterpretedData& aInterpretedData )
263{
264 // high/low/close
265 std::size_t nNumberOfNecessarySequences = 3;
266 // open
270 ++nNumberOfNecessarySequences;
271 // volume
272 bool bHasVolume = (( eVar == StockChartTypeTemplate::StockVariant::Volume ) ||
274
275 // 1. correct number of sub-types
276 if( aInterpretedData.Series.size() < (bHasVolume ? 2U : 1U ))
277 return false;
278
279 // 2. a. volume -- use default check
280 if( bHasVolume )
281 {
283 { std::vector< std::vector< rtl::Reference< DataSeries > > >{
284 aInterpretedData.Series[0] },
285 aInterpretedData.Categories }))
286 return false;
287 }
288
289 // 2. b. candlestick
290 {
291 OSL_ASSERT( aInterpretedData.Series.size() > (bHasVolume ? 1U : 0U));
292 const std::vector< rtl::Reference< DataSeries > > & aSeries = aInterpretedData.Series[(bHasVolume ? 1 : 0)];
293 if(aSeries.empty())
294 return false;
295 for( rtl::Reference< DataSeries > const & dataSeries : aSeries )
296 {
297 try
298 {
299 if( dataSeries->getDataSequences2().size() != nNumberOfNecessarySequences )
300 return false;
301 }
302 catch( const uno::Exception & )
303 {
304 DBG_UNHANDLED_EXCEPTION("chart2");
305 }
306 }
307 }
308
309 // 2. c. additional series
310 // ignore
311
312 return true;
313}
314
316 const InterpretedData& aInterpretedData )
317{
318 // prerequisite: StockDataInterpreter::isDataCompatible() returned true
319 return aInterpretedData;
320}
321
323 const OUString& sKey )
324{
325 if( sKey == "stock variant" )
326 {
328 std::map< StockChartTypeTemplate::StockVariant, sal_Int32 > aTranslation {
333 };
334 return uno::Any( aTranslation[eStockVariant] );
335 }
336 return uno::Any();
337}
338
339} // namespace chart
340
341/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static bool HasCategories(const css::uno::Sequence< css::beans::PropertyValue > &rArguments, const std::vector< css::uno::Reference< css::chart2::data::XLabeledDataSequence > > &rData)
static std::vector< css::uno::Reference< css::chart2::data::XLabeledDataSequence > > getDataSequences(const css::uno::Reference< css::chart2::data::XDataSource > &xSource)
static void SetRole(const css::uno::Reference< css::chart2::data::XDataSequence > &xSeq, const OUString &rRole)
virtual bool isDataCompatible(const InterpretedData &aInterpretedData)
parses the given data and states, if a reinterpretDataSeries() call can be done without data loss.
virtual InterpretedData reinterpretDataSeries(const InterpretedData &aInterpretedData) override
Re-interprets the data given in aInterpretedData while keeping the number of data series and the cate...
StockDataInterpreter(StockChartTypeTemplate::StockVariant eVariant)
virtual bool isDataCompatible(const InterpretedData &aInterpretedData) override
parses the given data and states, if a reinterpretDataSeries() call can be done without data loss.
virtual ~StockDataInterpreter() override
virtual css::uno::Any getChartTypeSpecificData(const OUString &sKey) override
Get chart information that is specific to a particular chart type, by key.
StockChartTypeTemplate::StockVariant GetStockVariant() const
virtual InterpretedData interpretDataSource(const css::uno::Reference< css::chart2::data::XDataSource > &xSource, const css::uno::Sequence< css::beans::PropertyValue > &aArguments, const std::vector< rtl::Reference< ::chart::DataSeries > > &aSeriesToReUse) override
Interprets the given data.
#define DBG_UNHANDLED_EXCEPTION(...)
constexpr OUStringLiteral aData
offers tooling to interpret different data sources in a structural and chart-type-dependent way.
std::vector< std::vector< rtl::Reference<::chart::DataSeries > > > Series
css::uno::Reference< css::chart2::data::XLabeledDataSequence > Categories