LibreOffice Module chart2 (master) 1
ScaleAutomatism.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 <ScaleAutomatism.hxx>
22#include <DateHelper.hxx>
23#include "DateScaling.hxx"
24#include <AxisHelper.hxx>
25#include <com/sun/star/chart/TimeUnit.hpp>
26#include <com/sun/star/chart2/AxisType.hpp>
27
28#include <rtl/math.hxx>
29#include <tools/long.hxx>
30#include <limits>
31
32namespace chart
33{
34using namespace ::com::sun::star;
35using namespace ::com::sun::star::chart2;
36using ::com::sun::star::chart::TimeUnit::DAY;
37using ::com::sun::star::chart::TimeUnit::MONTH;
38using ::com::sun::star::chart::TimeUnit::YEAR;
39
40const sal_Int32 MAXIMUM_MANUAL_INCREMENT_COUNT = 500;
41const sal_Int32 MAXIMUM_SUB_INCREMENT_COUNT = 100;
42
43static sal_Int32 lcl_getMaximumAutoIncrementCount( sal_Int32 nAxisType )
44{
45 sal_Int32 nMaximumAutoIncrementCount = 10;
46 if( nAxisType==AxisType::DATE )
47 nMaximumAutoIncrementCount = MAXIMUM_MANUAL_INCREMENT_COUNT;
48 return nMaximumAutoIncrementCount;
49}
50
51namespace
52{
53
54void lcl_ensureMaximumSubIncrementCount( sal_Int32& rnSubIntervalCount )
55{
56 if( rnSubIntervalCount > MAXIMUM_SUB_INCREMENT_COUNT )
57 rnSubIntervalCount = MAXIMUM_SUB_INCREMENT_COUNT;
58}
59
60}//end anonymous namespace
61
63 : Minimum(0.0)
64 , Maximum(10.0)
65 , Origin(0.0)
66 , Orientation(css::chart2::AxisOrientation_MATHEMATICAL)
67 , AxisType(css::chart2::AxisType::REALNUMBER)
68 , m_bShiftedCategoryPosition(false)
69 , TimeResolution(css::chart::TimeUnit::DAY)
70 , NullDate(30,12,1899)
71{
72}
73
75 : IntervalCount(2)
76 , PostEquidistant(true)
77{
78}
79
81 : MajorTimeInterval(1,css::chart::TimeUnit::DAY)
82 , MinorTimeInterval(1,css::chart::TimeUnit::DAY)
83 , Distance(1.0)
84 , PostEquidistant(true)
85 , BaseValue(0.0)
86{
87}
88
89ScaleAutomatism::ScaleAutomatism( const ScaleData& rSourceScale, const Date& rNullDate )
90 : m_aSourceScale( rSourceScale )
91 , m_fValueMinimum( 0.0 )
92 , m_fValueMaximum( 0.0 )
93 , m_nMaximumAutoMainIncrementCount( lcl_getMaximumAutoIncrementCount( rSourceScale.AxisType ) )
94 , m_bExpandBorderToIncrementRhythm( false )
95 , m_bExpandIfValuesCloseToBorder( false )
96 , m_bExpandWideValuesToZero( false )
97 , m_bExpandNarrowValuesTowardZero( false )
98 , m_nTimeResolution(css::chart::TimeUnit::DAY)
99 , m_aNullDate(rNullDate)
100{
102
103 double fExplicitOrigin = 0.0;
104 if( m_aSourceScale.Origin >>= fExplicitOrigin )
105 expandValueRange( fExplicitOrigin, fExplicitOrigin);
106}
107
109{
110 m_fValueMinimum = std::numeric_limits<double>::quiet_NaN();
111 m_fValueMaximum = std::numeric_limits<double>::quiet_NaN();
112}
113
114void ScaleAutomatism::expandValueRange( double fMinimum, double fMaximum )
115{
116 // if m_fValueMinimum and m_fValueMaximum == 0, it means that they were not determined.
117 // m_fValueMinimum == 0 makes impossible to determine real minimum,
118 // so they need to be reset tdf#96807
119 if( (m_fValueMinimum == 0.0) && (m_fValueMaximum == 0.0) )
121 if( (fMinimum < m_fValueMinimum) || std::isnan( m_fValueMinimum ) )
122 m_fValueMinimum = fMinimum;
123 if( (fMaximum > m_fValueMaximum) || std::isnan( m_fValueMaximum ) )
124 m_fValueMaximum = fMaximum;
125}
126
128 bool bExpandBorderToIncrementRhythm,
129 bool bExpandIfValuesCloseToBorder,
130 bool bExpandWideValuesToZero,
131 bool bExpandNarrowValuesTowardZero )
132{
133 // if called multiple times, enable an option, if it is set in at least one call
134 m_bExpandBorderToIncrementRhythm |= bExpandBorderToIncrementRhythm;
135 m_bExpandIfValuesCloseToBorder |= bExpandIfValuesCloseToBorder;
136 m_bExpandWideValuesToZero |= bExpandWideValuesToZero;
137 m_bExpandNarrowValuesTowardZero |= bExpandNarrowValuesTowardZero;
138
139 if( m_aSourceScale.AxisType==AxisType::PERCENT )
141}
142
143void ScaleAutomatism::setMaximumAutoMainIncrementCount( sal_Int32 nMaximumAutoMainIncrementCount )
144{
145 if( nMaximumAutoMainIncrementCount < 2 )
147 else if( nMaximumAutoMainIncrementCount > lcl_getMaximumAutoIncrementCount( m_aSourceScale.AxisType ) )
149 else
150 m_nMaximumAutoMainIncrementCount = nMaximumAutoMainIncrementCount;
151}
152
153void ScaleAutomatism::setAutomaticTimeResolution( sal_Int32 nTimeResolution )
154{
155 m_nTimeResolution = nTimeResolution;
156}
157
159 ExplicitScaleData& rExplicitScale, ExplicitIncrementData& rExplicitIncrement ) const
160{
161 // fill explicit scale
162 rExplicitScale.Orientation = m_aSourceScale.Orientation;
163 rExplicitScale.Scaling = m_aSourceScale.Scaling;
164 rExplicitScale.AxisType = m_aSourceScale.AxisType;
165 rExplicitScale.NullDate = m_aNullDate;
166
167 bool bAutoMinimum = !(m_aSourceScale.Minimum >>= rExplicitScale.Minimum);
168 bool bAutoMaximum = !(m_aSourceScale.Maximum >>= rExplicitScale.Maximum);
169 bool bAutoOrigin = !(m_aSourceScale.Origin >>= rExplicitScale.Origin);
170
171 // automatic scale minimum
172 if( bAutoMinimum )
173 {
174 if( m_aSourceScale.AxisType==AxisType::PERCENT )
175 rExplicitScale.Minimum = 0.0;
176 else if( std::isnan( m_fValueMinimum ) )
177 {
178 if( m_aSourceScale.AxisType==AxisType::DATE )
179 rExplicitScale.Minimum = 36526.0; //1.1.2000
180 else
181 rExplicitScale.Minimum = 0.0; //@todo get Minimum from scaling or from plotter????
182 }
183 else
184 rExplicitScale.Minimum = m_fValueMinimum;
185 }
186
187 // automatic scale maximum
188 if( bAutoMaximum )
189 {
190 if( m_aSourceScale.AxisType==AxisType::PERCENT )
191 rExplicitScale.Maximum = 1.0;
192 else if( std::isnan( m_fValueMaximum ) )
193 {
194 if( m_aSourceScale.AxisType==AxisType::DATE )
195 rExplicitScale.Maximum = 40179.0; //1.1.2010
196 else
197 rExplicitScale.Maximum = 10.0; //@todo get Maximum from scaling or from plotter????
198 }
199 else
200 rExplicitScale.Maximum = m_fValueMaximum;
201 }
202
203 //fill explicit increment
204
205 rExplicitScale.m_bShiftedCategoryPosition = m_aSourceScale.ShiftedCategoryPosition;
206 bool bIsLogarithm = false;
207
208 //minimum and maximum of the ExplicitScaleData may be changed if allowed
209 if( m_aSourceScale.AxisType==AxisType::DATE )
210 calculateExplicitIncrementAndScaleForDateTimeAxis( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum );
211 else if( m_aSourceScale.AxisType==AxisType::CATEGORY || m_aSourceScale.AxisType==AxisType::SERIES )
212 calculateExplicitIncrementAndScaleForCategory( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum );
213 else
214 {
215 bIsLogarithm = AxisHelper::isLogarithmic( rExplicitScale.Scaling );
216 if( bIsLogarithm )
217 calculateExplicitIncrementAndScaleForLogarithmic( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum );
218 else
219 calculateExplicitIncrementAndScaleForLinear( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum );
220 }
221
222 // automatic origin
223 if( bAutoOrigin )
224 {
225 // #i71415# automatic origin for logarithmic axis
226 double fDefaulOrigin = bIsLogarithm ? 1.0 : 0.0;
227
228 if( fDefaulOrigin < rExplicitScale.Minimum )
229 fDefaulOrigin = rExplicitScale.Minimum;
230 else if( fDefaulOrigin > rExplicitScale.Maximum )
231 fDefaulOrigin = rExplicitScale.Maximum;
232
233 rExplicitScale.Origin = fDefaulOrigin;
234 }
235}
236
238 ExplicitScaleData& rExplicitScale,
239 ExplicitIncrementData& rExplicitIncrement,
240 bool bAutoMinimum, bool bAutoMaximum ) const
241{
242 // no scaling for categories
243 rExplicitScale.Scaling.clear();
244
245 if( rExplicitScale.m_bShiftedCategoryPosition )
246 rExplicitScale.Maximum += 1.0;
247
248 // ensure that at least one category is visible
249 if( rExplicitScale.Maximum <= rExplicitScale.Minimum )
250 rExplicitScale.Maximum = rExplicitScale.Minimum + 1.0;
251
252 // default increment settings
253 rExplicitIncrement.PostEquidistant = true; // does not matter anyhow
254 rExplicitIncrement.Distance = 1.0; // category axis always have a main increment of 1
255 rExplicitIncrement.BaseValue = 0.0; // category axis always have a base of 0
256
257 // automatic minimum and maximum
258 if( bAutoMinimum && m_bExpandBorderToIncrementRhythm )
259 rExplicitScale.Minimum = EquidistantTickFactory::getMinimumAtIncrement( rExplicitScale.Minimum, rExplicitIncrement );
260 if( bAutoMaximum && m_bExpandBorderToIncrementRhythm )
261 rExplicitScale.Maximum = EquidistantTickFactory::getMaximumAtIncrement( rExplicitScale.Maximum, rExplicitIncrement );
262
263 //prevent performance killover
264 double fDistanceCount = ::rtl::math::approxFloor( (rExplicitScale.Maximum-rExplicitScale.Minimum) / rExplicitIncrement.Distance );
265 if( static_cast< sal_Int32 >( fDistanceCount ) > MAXIMUM_MANUAL_INCREMENT_COUNT )
266 {
267 double fMinimumFloor = ::rtl::math::approxFloor( rExplicitScale.Minimum );
268 double fMaximumCeil = ::rtl::math::approxCeil( rExplicitScale.Maximum );
269 rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / MAXIMUM_MANUAL_INCREMENT_COUNT );
270 }
271
272 //fill explicit sub increment
273 sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength();
274 for( sal_Int32 nN=0; nN<nSubCount; nN++ )
275 {
276 ExplicitSubIncrement aExplicitSubIncrement;
277 const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN];
278 if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount))
279 {
280 //scaling dependent
281 //@todo autocalculate IntervalCount dependent on MainIncrement and scaling
282 aExplicitSubIncrement.IntervalCount = 2;
283 }
284 lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount );
285 if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant))
286 {
287 //scaling dependent
288 aExplicitSubIncrement.PostEquidistant = false;
289 }
290 rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement);
291 }
292}
293
295 ExplicitScaleData& rExplicitScale,
296 ExplicitIncrementData& rExplicitIncrement,
297 bool bAutoMinimum, bool bAutoMaximum ) const
298{
299 // *** STEP 1: initialize the range data ***
300
301 const double fInputMinimum = rExplicitScale.Minimum;
302 const double fInputMaximum = rExplicitScale.Maximum;
303
304 double fSourceMinimum = rExplicitScale.Minimum;
305 double fSourceMaximum = rExplicitScale.Maximum;
306
307 // set automatic PostEquidistant to true (maybe scaling dependent?)
308 // Note: scaling with PostEquidistant==false is untested and needs review
309 if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) )
310 rExplicitIncrement.PostEquidistant = true;
311
312 /* All following scaling code will operate on the logarithms of the source
313 values. In the last step, the original values will be restored. */
314 uno::Reference< XScaling > xScaling = rExplicitScale.Scaling;
315 if( !xScaling.is() )
316 xScaling.set( AxisHelper::createLogarithmicScaling() );
317 uno::Reference< XScaling > xInverseScaling = xScaling->getInverseScaling();
318
319 fSourceMinimum = xScaling->doScaling( fSourceMinimum );
320 if( !std::isfinite( fSourceMinimum ) )
321 fSourceMinimum = 0.0;
322 else if( ::rtl::math::approxEqual( fSourceMinimum, ::rtl::math::approxFloor( fSourceMinimum ) ) )
323 fSourceMinimum = ::rtl::math::approxFloor( fSourceMinimum );
324
325 fSourceMaximum = xScaling->doScaling( fSourceMaximum );
326 if( !std::isfinite( fSourceMaximum ) )
327 fSourceMaximum = 0.0;
328 else if( ::rtl::math::approxEqual( fSourceMaximum, ::rtl::math::approxFloor( fSourceMaximum ) ) )
329 fSourceMaximum = ::rtl::math::approxFloor( fSourceMaximum );
330
331 /* If range is invalid (minimum greater than maximum), change one of the
332 variable limits to validate the range. In this step, a zero-sized range
333 is still allowed. */
334 if( fSourceMinimum > fSourceMaximum )
335 {
336 // force changing the maximum, if both limits are fixed
337 if( bAutoMaximum || !bAutoMinimum )
338 fSourceMaximum = fSourceMinimum;
339 else
340 fSourceMinimum = fSourceMaximum;
341 }
342
343 /* If maximum is less than 0 (and therefore minimum too), minimum and
344 maximum will be negated and swapped to make the following algorithms
345 easier. Example: Both ranges [2,5] and [-5,-2] will be processed as
346 [2,5], and the latter will be swapped back later. The range [0,0] is
347 explicitly excluded from swapping (this would result in [-1,0] instead
348 of the expected [0,1]). */
349 bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0);
350 if( bSwapAndNegateRange )
351 {
352 double fTempValue = fSourceMinimum;
353 fSourceMinimum = -fSourceMaximum;
354 fSourceMaximum = -fTempValue;
355 std::swap( bAutoMinimum, bAutoMaximum );
356 }
357
358 // *** STEP 2: find temporary (unrounded) axis minimum and maximum ***
359
360 double fTempMinimum = fSourceMinimum;
361 double fTempMaximum = fSourceMaximum;
362
363 /* If minimum is variable and greater than 0 (and therefore maximum too),
364 means all original values are greater than 1 (or all values are less
365 than 1, and the range has been swapped above), then: */
366 if( bAutoMinimum && (fTempMinimum > 0.0) )
367 {
368 double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum );
369 double fMaximumFloor = ::rtl::math::approxFloor( fTempMaximum );
370 // handle the exact value B^(n+1) to be in the range [B^n,B^(n+1)]
371 if( ::rtl::math::approxEqual( fTempMaximum, fMaximumFloor ) )
372 fMaximumFloor -= 1.0;
373
374 if( fMinimumFloor == fMaximumFloor )
375 {
376 /* if minimum and maximum are in one increment interval, expand
377 minimum toward 0 to make the 'shorter' data points visible. */
379 fTempMinimum -= 1.0;
380 }
381 }
382
383 /* If range is still zero-sized (e.g. when minimum is fixed), set minimum
384 to 0, which makes the axis start/stop at the value 1. */
385 if( fTempMinimum == fTempMaximum )
386 {
387 if( bAutoMinimum && (fTempMaximum > 0.0) )
388 fTempMinimum = 0.0;
389 else
390 fTempMaximum += 1.0; // always add one interval, even if maximum is fixed
391 }
392
393 // *** STEP 3: calculate main interval size ***
394
395 // base value (anchor position of the intervals), already scaled
396 if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) )
397 {
398 //scaling dependent
399 //@maybe todo is this default also plotter dependent ??
400 if( !bAutoMinimum )
401 rExplicitIncrement.BaseValue = fTempMinimum;
402 else if( !bAutoMaximum )
403 rExplicitIncrement.BaseValue = fTempMaximum;
404 else
405 rExplicitIncrement.BaseValue = 0.0;
406 }
407
408 // calculate automatic interval
409 bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance);
410 if( bAutoDistance )
411 rExplicitIncrement.Distance = 0.0;
412
413 /* Restrict number of allowed intervals with user-defined distance to
414 MAXIMUM_MANUAL_INCREMENT_COUNT. */
415 sal_Int32 nMaxMainIncrementCount = bAutoDistance ?
417
418 // repeat calculation until number of intervals are valid
419 bool bNeedIteration = true;
420 bool bHasCalculatedDistance = false;
421 while( bNeedIteration )
422 {
423 if( bAutoDistance )
424 {
425 // first iteration: calculate interval size from axis limits
426 if( !bHasCalculatedDistance )
427 {
428 double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum );
429 double fMaximumCeil = ::rtl::math::approxCeil( fTempMaximum );
430 rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / nMaxMainIncrementCount );
431 }
432 else
433 {
434 // following iterations: increase distance
435 rExplicitIncrement.Distance += 1.0;
436 }
437
438 // for next iteration: distance calculated -> use else path to increase
439 bHasCalculatedDistance = true;
440 }
441
442 // *** STEP 4: additional space above or below the data points ***
443
444 double fAxisMinimum = fTempMinimum;
445 double fAxisMaximum = fTempMaximum;
446
447 // round to entire multiples of the distance and add additional space
448 if( bAutoMinimum && m_bExpandBorderToIncrementRhythm )
449 {
450 fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement );
451
452 //ensure valid values after scaling #i100995#
453 if( !bAutoDistance )
454 {
455 double fCheck = xInverseScaling->doScaling( fAxisMinimum );
456 if( !std::isfinite( fCheck ) || fCheck <= 0 )
457 {
458 bAutoDistance = true;
459 bHasCalculatedDistance = false;
460 continue;
461 }
462 }
463 }
464 if( bAutoMaximum && m_bExpandBorderToIncrementRhythm )
465 {
466 fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement );
467
468 //ensure valid values after scaling #i100995#
469 if( !bAutoDistance )
470 {
471 double fCheck = xInverseScaling->doScaling( fAxisMaximum );
472 if( !std::isfinite( fCheck ) || fCheck <= 0 )
473 {
474 bAutoDistance = true;
475 bHasCalculatedDistance = false;
476 continue;
477 }
478 }
479 }
480
481 // set the resulting limits (swap back to negative range if needed)
482 if( bSwapAndNegateRange )
483 {
484 rExplicitScale.Minimum = -fAxisMaximum;
485 rExplicitScale.Maximum = -fAxisMinimum;
486 }
487 else
488 {
489 rExplicitScale.Minimum = fAxisMinimum;
490 rExplicitScale.Maximum = fAxisMaximum;
491 }
492
493 /* If the number of intervals is too high (e.g. due to invalid fixed
494 distance or due to added space above or below data points),
495 calculate again with increased distance. */
496 double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance );
497 bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount;
498 // if manual distance is invalid, trigger automatic calculation
499 if( bNeedIteration )
500 bAutoDistance = true;
501
502 // convert limits back to logarithmic scale
503 rExplicitScale.Minimum = xInverseScaling->doScaling( rExplicitScale.Minimum );
504 rExplicitScale.Maximum = xInverseScaling->doScaling( rExplicitScale.Maximum );
505
506 //ensure valid values after scaling #i100995#
507 if( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0)
508 {
509 rExplicitScale.Minimum = fInputMinimum;
510 if( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0 )
511 rExplicitScale.Minimum = 1.0;
512 }
513 if( !std::isfinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 )
514 {
515 rExplicitScale.Maximum= fInputMaximum;
516 if( !std::isfinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 )
517 rExplicitScale.Maximum = 10.0;
518 }
519 if( rExplicitScale.Maximum < rExplicitScale.Minimum )
520 std::swap( rExplicitScale.Maximum, rExplicitScale.Minimum );
521 }
522
523 //fill explicit sub increment
524 sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength();
525 for( sal_Int32 nN=0; nN<nSubCount; nN++ )
526 {
527 ExplicitSubIncrement aExplicitSubIncrement;
528 const SubIncrement& rSubIncrement = m_aSourceScale.IncrementData.SubIncrements[nN];
529 if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount))
530 {
531 //scaling dependent
532 //@todo autocalculate IntervalCount dependent on MainIncrement and scaling
533 aExplicitSubIncrement.IntervalCount = 9;
534 }
535 lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount );
536 if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant))
537 {
538 //scaling dependent
539 aExplicitSubIncrement.PostEquidistant = false;
540 }
541 rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement);
542 }
543}
544
546 ExplicitScaleData& rExplicitScale,
547 ExplicitIncrementData& rExplicitIncrement,
548 bool bAutoMinimum, bool bAutoMaximum ) const
549{
550 Date aMinDate(m_aNullDate); aMinDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Minimum));
551 Date aMaxDate(m_aNullDate); aMaxDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Maximum));
552 rExplicitIncrement.PostEquidistant = false;
553
554 if( aMinDate > aMaxDate )
555 {
556 std::swap(aMinDate,aMaxDate);
557 }
558
559 if( !(m_aSourceScale.TimeIncrement.TimeResolution >>= rExplicitScale.TimeResolution) )
560 rExplicitScale.TimeResolution = m_nTimeResolution;
561
562 rExplicitScale.Scaling = new DateScaling(m_aNullDate,rExplicitScale.TimeResolution,false);
563
564 // choose min and max suitable to time resolution
565 switch( rExplicitScale.TimeResolution )
566 {
567 case DAY:
568 if( rExplicitScale.m_bShiftedCategoryPosition )
569 ++aMaxDate; //for explicit scales we need one interval more (maximum excluded)
570 break;
571 case MONTH:
572 aMinDate.SetDay(1);
573 aMaxDate.SetDay(1);
574 if( rExplicitScale.m_bShiftedCategoryPosition )
575 aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded)
576 if( DateHelper::IsLessThanOneMonthAway( aMinDate, aMaxDate ) )
577 {
578 if( bAutoMaximum || !bAutoMinimum )
579 aMaxDate = DateHelper::GetDateSomeMonthsAway(aMinDate,1);
580 else
581 aMinDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,-1);
582 }
583 break;
584 case YEAR:
585 aMinDate.SetDay(1);
586 aMinDate.SetMonth(1);
587 aMaxDate.SetDay(1);
588 aMaxDate.SetMonth(1);
589 if( rExplicitScale.m_bShiftedCategoryPosition )
590 aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded)
591 if( DateHelper::IsLessThanOneYearAway( aMinDate, aMaxDate ) )
592 {
593 if( bAutoMaximum || !bAutoMinimum )
594 aMaxDate = DateHelper::GetDateSomeYearsAway(aMinDate,1);
595 else
596 aMinDate = DateHelper::GetDateSomeYearsAway(aMaxDate,-1);
597 }
598 break;
599 }
600
601 // set the resulting limits (swap back to negative range if needed)
602 rExplicitScale.Minimum = aMinDate - m_aNullDate;
603 rExplicitScale.Maximum = aMaxDate - m_aNullDate;
604
605 bool bAutoMajor = !(m_aSourceScale.TimeIncrement.MajorTimeInterval >>= rExplicitIncrement.MajorTimeInterval);
606 bool bAutoMinor = !(m_aSourceScale.TimeIncrement.MinorTimeInterval >>= rExplicitIncrement.MinorTimeInterval);
607
608 sal_Int32 nMaxMainIncrementCount = bAutoMajor ?
610 if( nMaxMainIncrementCount > 1 )
611 nMaxMainIncrementCount--;
612
613 //choose major time interval:
614 tools::Long nDayCount = aMaxDate - aMinDate;
615 tools::Long nMainIncrementCount = 1;
616 if( !bAutoMajor )
617 {
618 tools::Long nIntervalDayCount = rExplicitIncrement.MajorTimeInterval.Number;
619 if( rExplicitIncrement.MajorTimeInterval.TimeUnit < rExplicitScale.TimeResolution )
620 rExplicitIncrement.MajorTimeInterval.TimeUnit = rExplicitScale.TimeResolution;
621 switch( rExplicitIncrement.MajorTimeInterval.TimeUnit )
622 {
623 case DAY:
624 break;
625 case MONTH:
626 nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
627 break;
628 case YEAR:
629 nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
630 break;
631 }
632 nMainIncrementCount = nDayCount/nIntervalDayCount;
633 if( nMainIncrementCount > nMaxMainIncrementCount )
634 bAutoMajor = true;
635 }
636 if( bAutoMajor )
637 {
638 tools::Long nNumer = 1;
639 tools::Long nIntervalDays = nDayCount / nMaxMainIncrementCount;
640 double nDaysPerInterval = 1.0;
641 if( nIntervalDays>365 || rExplicitScale.TimeResolution==YEAR )
642 {
643 rExplicitIncrement.MajorTimeInterval.TimeUnit = YEAR;
644 nDaysPerInterval = 365.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
645 }
646 else if( nIntervalDays>31 || rExplicitScale.TimeResolution==MONTH )
647 {
648 rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH;
649 nDaysPerInterval = 31.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
650 }
651 else
652 {
653 rExplicitIncrement.MajorTimeInterval.TimeUnit = DAY;
654 nDaysPerInterval = 1.0;
655 }
656
657 nNumer = static_cast<sal_Int32>( rtl::math::approxFloor( nIntervalDays/nDaysPerInterval ) );
658 if(nNumer<=0)
659 nNumer=1;
660 if( rExplicitIncrement.MajorTimeInterval.TimeUnit == DAY )
661 {
662 if( nNumer>2 && nNumer<7 )
663 nNumer=7;
664 else if( nNumer>7 )
665 {
666 rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH;
667 nDaysPerInterval = 31.0;
668 nNumer = static_cast<sal_Int32>( rtl::math::approxFloor( nIntervalDays/nDaysPerInterval ) );
669 if(nNumer<=0)
670 nNumer=1;
671 }
672 }
673 rExplicitIncrement.MajorTimeInterval.Number = nNumer;
674 nMainIncrementCount = static_cast<tools::Long>(nDayCount/(nNumer*nDaysPerInterval));
675 }
676
677 //choose minor time interval:
678 if( !bAutoMinor )
679 {
680 if( rExplicitIncrement.MinorTimeInterval.TimeUnit > rExplicitIncrement.MajorTimeInterval.TimeUnit )
681 rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit;
682 tools::Long nIntervalDayCount = rExplicitIncrement.MinorTimeInterval.Number;
683 switch( rExplicitIncrement.MinorTimeInterval.TimeUnit )
684 {
685 case DAY:
686 break;
687 case MONTH:
688 nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
689 break;
690 case YEAR:
691 nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ...
692 break;
693 }
694 if( nDayCount/nIntervalDayCount > nMaxMainIncrementCount )
695 bAutoMinor = true;
696 }
697 if( !bAutoMinor )
698 return;
699
700 rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit;
701 rExplicitIncrement.MinorTimeInterval.Number = 1;
702 if( nMainIncrementCount > 100 )
703 rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number;
704 else
705 {
706 if( rExplicitIncrement.MajorTimeInterval.Number >= 2 )
707 {
708 if( !(rExplicitIncrement.MajorTimeInterval.Number%2) )
709 rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/2;
710 else if( !(rExplicitIncrement.MajorTimeInterval.Number%3) )
711 rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/3;
712 else if( !(rExplicitIncrement.MajorTimeInterval.Number%5) )
713 rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/5;
714 else if( rExplicitIncrement.MajorTimeInterval.Number > 50 )
715 rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number;
716 }
717 else
718 {
719 switch( rExplicitIncrement.MajorTimeInterval.TimeUnit )
720 {
721 case DAY:
722 break;
723 case MONTH:
724 if( rExplicitScale.TimeResolution == DAY )
725 rExplicitIncrement.MinorTimeInterval.TimeUnit = DAY;
726 break;
727 case YEAR:
728 if( rExplicitScale.TimeResolution <= MONTH )
729 rExplicitIncrement.MinorTimeInterval.TimeUnit = MONTH;
730 break;
731 }
732 }
733 }
734
735}
736
738 ExplicitScaleData& rExplicitScale,
739 ExplicitIncrementData& rExplicitIncrement,
740 bool bAutoMinimum, bool bAutoMaximum ) const
741{
742 // *** STEP 1: initialize the range data ***
743
744 double fSourceMinimum = rExplicitScale.Minimum;
745 double fSourceMaximum = rExplicitScale.Maximum;
746
747 // set automatic PostEquidistant to true (maybe scaling dependent?)
748 if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) )
749 rExplicitIncrement.PostEquidistant = true;
750
751 /* If range is invalid (minimum greater than maximum), change one of the
752 variable limits to validate the range. In this step, a zero-sized range
753 is still allowed. */
754 if( fSourceMinimum > fSourceMaximum )
755 {
756 // force changing the maximum, if both limits are fixed
757 if( bAutoMaximum || !bAutoMinimum )
758 fSourceMaximum = fSourceMinimum;
759 else
760 fSourceMinimum = fSourceMaximum;
761 }
762
763 /* If maximum is zero or negative (and therefore minimum too), minimum and
764 maximum will be negated and swapped to make the following algorithms
765 easier. Example: Both ranges [2,5] and [-5,-2] will be processed as
766 [2,5], and the latter will be swapped back later. The range [0,0] is
767 explicitly excluded from swapping (this would result in [-1,0] instead
768 of the expected [0,1]). */
769 bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0);
770 if( bSwapAndNegateRange )
771 {
772 double fTempValue = fSourceMinimum;
773 fSourceMinimum = -fSourceMaximum;
774 fSourceMaximum = -fTempValue;
775 std::swap( bAutoMinimum, bAutoMaximum );
776 }
777
778 // *** STEP 2: find temporary (unrounded) axis minimum and maximum ***
779
780 double fTempMinimum = fSourceMinimum;
781 double fTempMaximum = fSourceMaximum;
782
783 /* If minimum is variable and greater than 0 (and therefore maximum too),
784 means all values are positive (or all values are negative, and the
785 range has been swapped above), then: */
786 if( bAutoMinimum && (fTempMinimum > 0.0) )
787 {
788 /* If minimum equals maximum, or if minimum is less than 5/6 of
789 maximum, set minimum to 0. */
790 if( (fTempMinimum == fTempMaximum) || (fTempMinimum / fTempMaximum < 5.0 / 6.0) )
791 {
793 fTempMinimum = 0.0;
794 }
795 /* Else (minimum is greater than or equal to 5/6 of maximum), add half
796 of the visible range (expand minimum toward 0) to make the
797 'shorter' data points visible. */
798 else
799 {
801 fTempMinimum -= (fTempMaximum - fTempMinimum) / 2.0;
802 }
803 }
804
805 /* If range is still zero-sized (e.g. when minimum is fixed), add some
806 space to a variable limit. */
807 if( fTempMinimum == fTempMaximum )
808 {
809 if( bAutoMaximum || !bAutoMinimum )
810 {
811 // change 0 to 1, otherwise double the value
812 if( fTempMaximum == 0.0 )
813 fTempMaximum = 1.0;
814 else
815 fTempMaximum *= 2.0;
816 }
817 else
818 {
819 // change 0 to -1, otherwise halve the value
820 if( fTempMinimum == 0.0 )
821 fTempMinimum = -1.0;
822 else
823 fTempMinimum /= 2.0;
824 }
825 }
826
827 // *** STEP 3: calculate main interval size ***
828
829 // base value (anchor position of the intervals)
830 if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) )
831 {
832 if( !bAutoMinimum )
833 rExplicitIncrement.BaseValue = fTempMinimum;
834 else if( !bAutoMaximum )
835 rExplicitIncrement.BaseValue = fTempMaximum;
836 else
837 rExplicitIncrement.BaseValue = 0.0;
838 }
839
840 // calculate automatic interval
841 bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance);
842 /* Restrict number of allowed intervals with user-defined distance to
843 MAXIMUM_MANUAL_INCREMENT_COUNT. */
844 sal_Int32 nMaxMainIncrementCount = bAutoDistance ?
846
847 double fDistanceMagnitude = 0.0;
848 double fDistanceNormalized = 0.0;
849 bool bHasNormalizedDistance = false;
850
851 // repeat calculation until number of intervals are valid
852 bool bNeedIteration = true;
853 while( bNeedIteration )
854 {
855 if( bAutoDistance )
856 {
857 // first iteration: calculate interval size from axis limits
858 if( !bHasNormalizedDistance )
859 {
860 // raw size of an interval
861 double fDistance = (fTempMaximum - fTempMinimum) / nMaxMainIncrementCount;
862
863 // if distance of is less than 1e-307, do not do anything
864 if( fDistance <= 1.0e-307 )
865 {
866 fDistanceNormalized = 1.0;
867 fDistanceMagnitude = 1.0e-307;
868 }
869 else if ( !std::isfinite(fDistance) )
870 {
871 // fdo#43703: Handle values bigger than limits correctly
872 fDistanceNormalized = 1.0;
873 fDistanceMagnitude = std::numeric_limits<double>::max();
874 }
875 else
876 {
877 // distance magnitude (a power of 10)
878 int nExponent = static_cast< int >( ::rtl::math::approxFloor( log10( fDistance ) ) );
879 fDistanceMagnitude = ::rtl::math::pow10Exp( 1.0, nExponent );
880
881 // stick normalized distance to a few predefined values
882 fDistanceNormalized = fDistance / fDistanceMagnitude;
883 if( fDistanceNormalized <= 1.0 )
884 fDistanceNormalized = 1.0;
885 else if( fDistanceNormalized <= 2.0 )
886 fDistanceNormalized = 2.0;
887 else if( fDistanceNormalized <= 5.0 )
888 fDistanceNormalized = 5.0;
889 else
890 {
891 fDistanceNormalized = 1.0;
892 fDistanceMagnitude *= 10;
893 }
894 }
895 // for next iteration: distance is normalized -> use else path to increase distance
896 bHasNormalizedDistance = true;
897 }
898 // following iterations: increase distance, use only allowed values
899 else
900 {
901 if( fDistanceNormalized == 1.0 )
902 fDistanceNormalized = 2.0;
903 else if( fDistanceNormalized == 2.0 )
904 fDistanceNormalized = 5.0;
905 else
906 {
907 fDistanceNormalized = 1.0;
908 fDistanceMagnitude *= 10;
909 }
910 }
911
912 // set the resulting distance
913 rExplicitIncrement.Distance = fDistanceNormalized * fDistanceMagnitude;
914 }
915
916 // *** STEP 4: additional space above or below the data points ***
917
918 double fAxisMinimum = fTempMinimum;
919 double fAxisMaximum = fTempMaximum;
920
921 // round to entire multiples of the distance and add additional space
922 if( bAutoMinimum )
923 {
924 // round to entire multiples of the distance, based on the base value
926 fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement );
927 // additional space, if source minimum is to near at axis minimum
929 if( (fAxisMinimum != 0.0) && ((fAxisMaximum - fSourceMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) )
930 fAxisMinimum -= rExplicitIncrement.Distance;
931 }
932 if( bAutoMaximum )
933 {
934 // round to entire multiples of the distance, based on the base value
936 fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement );
937 // additional space, if source maximum is to near at axis maximum
939 if( (fAxisMaximum != 0.0) && ((fSourceMaximum - fAxisMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) )
940 fAxisMaximum += rExplicitIncrement.Distance;
941 }
942
943 // set the resulting limits (swap back to negative range if needed)
944 if( bSwapAndNegateRange )
945 {
946 rExplicitScale.Minimum = -fAxisMaximum;
947 rExplicitScale.Maximum = -fAxisMinimum;
948 }
949 else
950 {
951 rExplicitScale.Minimum = fAxisMinimum;
952 rExplicitScale.Maximum = fAxisMaximum;
953 }
954
955 /* If the number of intervals is too high (e.g. due to invalid fixed
956 distance or due to added space above or below data points),
957 calculate again with increased distance. */
958 double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance );
959 bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount;
960 // if manual distance is invalid, trigger automatic calculation
961 if( bNeedIteration )
962 bAutoDistance = true;
963 }
964
965 //fill explicit sub increment
966 sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength();
967 for( sal_Int32 nN=0; nN<nSubCount; nN++ )
968 {
969 ExplicitSubIncrement aExplicitSubIncrement;
970 const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN];
971 if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount))
972 {
973 //scaling dependent
974 //@todo autocalculate IntervalCount dependent on MainIncrement and scaling
975 aExplicitSubIncrement.IntervalCount = 2;
976 }
977 lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount );
978 if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant))
979 {
980 //scaling dependent
981 aExplicitSubIncrement.PostEquidistant = false;
982 }
983 rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement);
984 }
985}
986
987} //namespace chart
988
989/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void AddDays(sal_Int32 nAddDays)
void SetMonth(sal_uInt16 nNewMonth)
void SetDay(sal_uInt16 nNewDay)
static bool isLogarithmic(const css::uno::Reference< css::chart2::XScaling > &xScaling)
Definition: AxisHelper.cxx:96
static css::uno::Reference< css::chart2::XScaling > createLogarithmicScaling(double fBase=10.0)
Definition: AxisHelper.cxx:70
static Date GetDateSomeYearsAway(const Date &rD, sal_Int32 nYearDistance)
Definition: DateHelper.cxx:46
static Date GetDateSomeMonthsAway(const Date &rD, sal_Int32 nMonthDistance)
Definition: DateHelper.cxx:39
static bool IsLessThanOneYearAway(const Date &rD1, const Date &rD2)
Definition: DateHelper.cxx:61
static bool IsLessThanOneMonthAway(const Date &rD1, const Date &rD2)
Definition: DateHelper.cxx:53
static double getMinimumAtIncrement(double fMin, const ExplicitIncrementData &rIncrement)
static double getMaximumAtIncrement(double fMax, const ExplicitIncrementData &rIncrement)
void calculateExplicitIncrementAndScaleForLinear(ExplicitScaleData &rExplicitScale, ExplicitIncrementData &rExplicitIncrement, bool bAutoMinimum, bool bAutoMaximum) const
Fills the passed scale data and increment data for linear scaling.
ScaleAutomatism(const css::chart2::ScaleData &rSourceScale, const Date &rNullDate)
bool m_bExpandBorderToIncrementRhythm
Maximum number of automatic main increments.
void calculateExplicitScaleAndIncrement(ExplicitScaleData &rExplicitScale, ExplicitIncrementData &rExplicitIncrement) const
Fills the passed scale data and increment data according to the own settings.
sal_Int32 m_nTimeResolution
true = Expand narrow range toward zero (add half of range).
void calculateExplicitIncrementAndScaleForDateTimeAxis(ExplicitScaleData &rExplicitScale, ExplicitIncrementData &rExplicitIncrement, bool bAutoMinimum, bool bAutoMaximum) const
Fills the passed scale data and increment data for date-time axis.
void setAutoScalingOptions(bool bExpandBorderToIncrementRhythm, bool bExpandIfValuesCloseToBorder, bool bExpandWideValuesToZero, bool bExpandNarrowValuesTowardZero)
Sets additional auto scaling options.
void calculateExplicitIncrementAndScaleForCategory(ExplicitScaleData &rExplicitScale, ExplicitIncrementData &rExplicitIncrement, bool bAutoMinimum, bool bAutoMaximum) const
Fills the passed scale data and increment data for category scaling.
void calculateExplicitIncrementAndScaleForLogarithmic(ExplicitScaleData &rExplicitScale, ExplicitIncrementData &rExplicitIncrement, bool bAutoMinimum, bool bAutoMaximum) const
Fills the passed scale data and increment data for logarithmic scaling.
void setMaximumAutoMainIncrementCount(sal_Int32 nMaximumAutoMainIncrementCount)
Sets the maximum allowed number of automatic main increments.
bool m_bExpandNarrowValuesTowardZero
true = Expand wide spread values to zero.
sal_Int32 m_nMaximumAutoMainIncrementCount
Maximum of all source values.
bool m_bExpandWideValuesToZero
true = Expand if values are too close to the borders.
void setAutomaticTimeResolution(sal_Int32 nTimeResolution)
Sets the time resolution to be used in case it is not set explicitly within the scale.
bool m_bExpandIfValuesCloseToBorder
true = Expand to main increments.
void expandValueRange(double fMinimum, double fMaximum)
Expands own value range with the passed minimum and maximum.
double m_fValueMaximum
Minimum of all source values.
css::chart2::ScaleData m_aSourceScale
static sal_Int32 lcl_getMaximumAutoIncrementCount(sal_Int32 nAxisType)
const sal_Int32 MAXIMUM_MANUAL_INCREMENT_COUNT
const sal_Int32 MAXIMUM_SUB_INCREMENT_COUNT
AxisType
long Long
Orientation
describes how tickmarks are positioned on the scale of an axis.
std::vector< ExplicitSubIncrement > SubIncrements
<member>SubIncrements</member> describes the positioning of further sub tickmarks on the scale of an ...
double Distance
the other members are for not date-time axis
css::chart::TimeInterval MajorTimeInterval
the following two members are only for date-time axis
css::chart::TimeInterval MinorTimeInterval
bool PostEquidistant
<member>PostEquidistant</member> rules whether the member <member>Distance</member> describes a dista...
double BaseValue
The <member>BaseValue</member> gives a starting point on the scale to which all further main tickmark...
This structure contains the explicit values for a scale like Minimum and Maximum.
css::uno::Reference< css::chart2::XScaling > Scaling
css::chart2::AxisOrientation Orientation
sal_Int32 IntervalCount
Numbers of intervals between two superior ticks.
bool PostEquidistant
If <TRUE>, the distance between two sub-tick-marks on the screen is always the same.