地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

874 lines
22 KiB

15 years ago
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
* Qwt Widget Library
* Copyright (C) 1997 Josef Wilgen
* Copyright (C) 2002 Uwe Rathmann
*
15 years ago
* This library is free software; you can redistribute it and/or
* modify it under the terms of the Qwt License, Version 1.0
*****************************************************************************/
#include "qwt_math.h"
#include "qwt_scale_map.h"
#include "qwt_scale_engine.h"
static const double _eps = 1.0e-6;
/*!
\brief Compare 2 values, relative to an interval
Values are "equal", when :
\f$\cdot value2 - value1 <= abs(intervalSize * 10e^{-6})\f$
\param value1 First value to compare
\param value2 Second value to compare
\param intervalSize interval size
\return 0: if equal, -1: if value2 > value1, 1: if value1 > value2
*/
int QwtScaleArithmetic::compareEps(double value1, double value2,
double intervalSize)
15 years ago
{
const double eps = qwtAbs(_eps * intervalSize);
if ( value2 - value1 > eps )
return -1;
if ( value1 - value2 > eps )
return 1;
return 0;
}
/*!
Ceil a value, relative to an interval
\param value Value to ceil
\param intervalSize Interval size
15 years ago
\sa floorEps
*/
double QwtScaleArithmetic::ceilEps(double value,
double intervalSize)
15 years ago
{
const double eps = _eps * intervalSize;
value = (value - eps) / intervalSize;
return ceil(value) * intervalSize;
}
/*!
Floor a value, relative to an interval
\param value Value to floor
\param intervalSize Interval size
15 years ago
\sa floorEps
*/
double QwtScaleArithmetic::floorEps(double value, double intervalSize)
15 years ago
{
const double eps = _eps * intervalSize;
value = (value + eps) / intervalSize;
return floor(value) * intervalSize;
}
/*
\brief Divide an interval into steps
\f$stepSize = (intervalSize - intervalSize * 10e^{-6}) / numSteps\f$
\param intervalSize Interval size
\param numSteps Number of steps
\return Step size
*/
double QwtScaleArithmetic::divideEps(double intervalSize, double numSteps)
15 years ago
{
if ( numSteps == 0.0 || intervalSize == 0.0 )
return 0.0;
return (intervalSize - (_eps * intervalSize)) / numSteps;
}
15 years ago
/*!
Find the smallest value out of {1,2,5}*10^n with an integer number n
which is greater than or equal to x
15 years ago
\param x Input value
*/
double QwtScaleArithmetic::ceil125(double x)
15 years ago
{
if (x == 0.0)
15 years ago
return 0.0;
const double sign = (x > 0) ? 1.0 : -1.0;
const double lx = log10(fabs(x));
const double p10 = floor(lx);
15 years ago
double fr = pow(10.0, lx - p10);
if (fr <=1.0)
fr = 1.0;
15 years ago
else if (fr <= 2.0)
fr = 2.0;
else if (fr <= 5.0)
fr = 5.0;
15 years ago
else
fr = 10.0;
15 years ago
return sign * fr * pow(10.0, p10);
}
/*!
\brief Find the largest value out of {1,2,5}*10^n with an integer number n
which is smaller than or equal to x
\param x Input value
*/
double QwtScaleArithmetic::floor125(double x)
15 years ago
{
if (x == 0.0)
return 0.0;
double sign = (x > 0) ? 1.0 : -1.0;
const double lx = log10(fabs(x));
const double p10 = floor(lx);
double fr = pow(10.0, lx - p10);
if (fr >= 10.0)
fr = 10.0;
15 years ago
else if (fr >= 5.0)
fr = 5.0;
15 years ago
else if (fr >= 2.0)
fr = 2.0;
15 years ago
else
fr = 1.0;
15 years ago
return sign * fr * pow(10.0, p10);
}
class QwtScaleEngine::PrivateData
{
public:
PrivateData():
attributes(QwtScaleEngine::NoAttribute),
loMargin(0.0),
hiMargin(0.0),
referenceValue(0.0) {
15 years ago
}
int attributes; // scale attributes
double loMargin; // margins
double hiMargin;
double referenceValue; // reference value
};
//! Ctor
QwtScaleEngine::QwtScaleEngine()
{
d_data = new PrivateData;
}
//! Dtor
QwtScaleEngine::~QwtScaleEngine ()
{
delete d_data;
}
/*!
\return the margin at the lower end of the scale
The default margin is 0.
\sa QwtScaleEngine::setMargins()
*/
double QwtScaleEngine::loMargin() const
{
return d_data->loMargin;
15 years ago
}
/*!
\return the margin at the upper end of the scale
The default margin is 0.
\sa QwtScaleEngine::setMargins()
*/
double QwtScaleEngine::hiMargin() const
{
return d_data->hiMargin;
15 years ago
}
/*!
\brief Specify margins at the scale's endpoints
\param mlo minimum distance between the scale's lower boundary and the
smallest enclosed value
\param mhi minimum distance between the scale's upper boundary and the
greatest enclosed value
Margins can be used to leave a minimum amount of space between
the enclosed intervals and the boundaries of the scale.
\warning
\li QwtLog10ScaleEngine measures the margins in decades.
\sa QwtScaleEngine::hiMargin, QwtScaleEngine::loMargin
*/
void QwtScaleEngine::setMargins(double mlo, double mhi)
{
d_data->loMargin = qwtMax(mlo,0.0);
d_data->hiMargin = qwtMax(mhi,0.0);
}
/*!
Calculate a step size for an interval size
\param intervalSize Interval size
\param numSteps Number of steps
15 years ago
\return Step size
*/
double QwtScaleEngine::divideInterval(
double intervalSize, int numSteps) const
{
if ( numSteps <= 0 )
return 0.0;
double v = QwtScaleArithmetic::divideEps(intervalSize, numSteps);
return QwtScaleArithmetic::ceil125(v);
}
/*!
Check if an interval "contains" a value
\param interval Interval
\param value Value
\sa QwtScaleArithmetic::compareEps
*/
bool QwtScaleEngine::contains(
const QwtDoubleInterval &interval, double value) const
{
if (!interval.isValid() )
return false;
if ( QwtScaleArithmetic::compareEps(value,
interval.minValue(), interval.width()) < 0 ) {
15 years ago
return false;
}
if ( QwtScaleArithmetic::compareEps(value,
interval.maxValue(), interval.width()) > 0 ) {
15 years ago
return false;
}
return true;
}
/*!
Remove ticks from a list, that are not inside an interval
\param ticks Tick list
\param interval Interval
\return Stripped tick list
*/
QwtValueList QwtScaleEngine::strip(
const QwtValueList& ticks,
15 years ago
const QwtDoubleInterval &interval) const
{
if ( !interval.isValid() || ticks.count() == 0 )
return QwtValueList();
if ( contains(interval, ticks.first())
&& contains(interval, ticks.last()) ) {
15 years ago
return ticks;
}
QwtValueList strippedTicks;
for ( int i = 0; i < (int)ticks.count(); i++ ) {
15 years ago
if ( contains(interval, ticks[i]) )
strippedTicks += ticks[i];
}
return strippedTicks;
}
/*!
\brief Build an interval for a value
In case of v == 0.0 the interval is [-0.5, 0.5],
otherwide it is [0.5 * v, 1.5 * v]
*/
QwtDoubleInterval QwtScaleEngine::buildInterval(double v) const
{
const double delta = (v == 0.0) ? 0.5 : qwtAbs(0.5 * v);
return QwtDoubleInterval(v - delta, v + delta);
}
/*!
Change a scale attribute
\param attribute Attribute to change
\param on On/Off
The behaviour of the scale engine can be changed
with the following attributes:
<dl>
<dt>QwtScaleEngine::IncludeReference
<dd>Build a scale which includes the reference value.
<dt>QwtScaleEngine::Symmetric
<dd>Build a scale which is symmetric to the reference value.
<dt>QwtScaleEngine::Floating
<dd>The endpoints of the scale are supposed to be equal the outmost included
values plus the specified margins (see setMargins()). If this attribute is
*not* set, the endpoints of the scale will be integer multiples of the step
size.
<dt>QwtScaleEngine::Inverted
<dd>Turn the scale upside down.
</dl>
\sa QwtScaleEngine::testAttribute()
*/
void QwtScaleEngine::setAttribute(Attribute attribute, bool on)
{
if (on)
d_data->attributes |= attribute;
15 years ago
else
d_data->attributes &= (~attribute);
15 years ago
}
/*!
Check if a attribute is set.
\param attribute Attribute to be tested
\sa QwtScaleEngine::setAttribute() for a description of the possible options.
*/
bool QwtScaleEngine::testAttribute(Attribute attribute) const
{
return bool(d_data->attributes & attribute);
}
/*!
Change the scale attribute
\param attributes Set scale attributes
\sa QwtScaleEngine::attributes()
*/
void QwtScaleEngine::setAttributes(int attributes)
{
d_data->attributes = attributes;
}
/*!
Return the scale attributes
*/
int QwtScaleEngine::attributes() const
{
return d_data->attributes;
}
/*!
\brief Specify a reference point
\param r new reference value
The reference point is needed if options IncludeRef or
Symmetric are active. Its default value is 0.0.
*/
void QwtScaleEngine::setReference(double r)
{
d_data->referenceValue = r;
}
/*!
\return the reference value
\sa QwtScaleEngine::setReference(), QwtScaleEngine::setAttribute()
*/
double QwtScaleEngine::reference() const
{
return d_data->referenceValue;
15 years ago
}
/*!
Return a transformation, for linear scales
*/
QwtScaleTransformation *QwtLinearScaleEngine::transformation() const
{
return new QwtScaleTransformation(QwtScaleTransformation::Linear);
}
/*!
Align and divide an interval
15 years ago
\param maxNumSteps Max. number of steps
\param x1 First limit of the interval (In/Out)
\param x2 Second limit of the interval (In/Out)
\param stepSize Step size (Out)
\sa QwtLinearScaleEngine::setAttribute
*/
void QwtLinearScaleEngine::autoScale(int maxNumSteps,
double &x1, double &x2, double &stepSize) const
15 years ago
{
QwtDoubleInterval interval(x1, x2);
interval = interval.normalized();
interval.setMinValue(interval.minValue() - loMargin());
interval.setMaxValue(interval.maxValue() + hiMargin());
if (testAttribute(QwtScaleEngine::Symmetric))
interval = interval.symmetrize(reference());
15 years ago
if (testAttribute(QwtScaleEngine::IncludeReference))
interval = interval.extend(reference());
if (interval.width() == 0.0)
interval = buildInterval(interval.minValue());
stepSize = divideInterval(interval.width(), qwtMax(maxNumSteps, 1));
if ( !testAttribute(QwtScaleEngine::Floating) )
interval = align(interval, stepSize);
x1 = interval.minValue();
x2 = interval.maxValue();
if (testAttribute(QwtScaleEngine::Inverted)) {
15 years ago
qSwap(x1, x2);
stepSize = -stepSize;
}
}
/*!
\brief Calculate a scale division
\param x1 First interval limit
\param x2 Second interval limit
15 years ago
\param maxMajSteps Maximum for the number of major steps
\param maxMinSteps Maximum number of minor steps
\param stepSize Step size. If stepSize == 0, the scaleEngine
calculates one.
\sa QwtScaleEngine::stepSize, QwtScaleEngine::subDivide
*/
QwtScaleDiv QwtLinearScaleEngine::divideScale(double x1, double x2,
int maxMajSteps, int maxMinSteps, double stepSize) const
15 years ago
{
QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized();
if (interval.width() <= 0 )
return QwtScaleDiv();
stepSize = qwtAbs(stepSize);
if ( stepSize == 0.0 ) {
15 years ago
if ( maxMajSteps < 1 )
maxMajSteps = 1;
stepSize = divideInterval(interval.width(), maxMajSteps);
}
QwtScaleDiv scaleDiv;
if ( stepSize != 0.0 ) {
15 years ago
QwtValueList ticks[QwtScaleDiv::NTickTypes];
buildTicks(interval, stepSize, maxMinSteps, ticks);
scaleDiv = QwtScaleDiv(interval, ticks);
}
if ( x1 > x2 )
scaleDiv.invert();
return scaleDiv;
}
void QwtLinearScaleEngine::buildTicks(
const QwtDoubleInterval& interval, double stepSize, int maxMinSteps,
QwtValueList ticks[QwtScaleDiv::NTickTypes]) const
{
const QwtDoubleInterval boundingInterval =
align(interval, stepSize);
ticks[QwtScaleDiv::MajorTick] =
15 years ago
buildMajorTicks(boundingInterval, stepSize);
if ( maxMinSteps > 0 ) {
15 years ago
buildMinorTicks(ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize,
ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick]);
15 years ago
}
for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) {
15 years ago
ticks[i] = strip(ticks[i], interval);
// ticks very close to 0.0 are
15 years ago
// explicitely set to 0.0
for ( int j = 0; j < (int)ticks[i].count(); j++ ) {
15 years ago
if ( QwtScaleArithmetic::compareEps(ticks[i][j], 0.0, stepSize) == 0 )
ticks[i][j] = 0.0;
}
}
}
QwtValueList QwtLinearScaleEngine::buildMajorTicks(
const QwtDoubleInterval &interval, double stepSize) const
{
int numTicks = qRound(interval.width() / stepSize) + 1;
#if 1
if ( numTicks > 10000 )
numTicks = 10000;
#endif
QwtValueList ticks;
ticks += interval.minValue();
for (int i = 1; i < numTicks - 1; i++)
ticks += interval.minValue() + i * stepSize;
ticks += interval.maxValue();
return ticks;
}
void QwtLinearScaleEngine::buildMinorTicks(
const QwtValueList& majorTicks,
int maxMinSteps, double stepSize,
QwtValueList &minorTicks,
15 years ago
QwtValueList &mediumTicks) const
{
15 years ago
double minStep = divideInterval(stepSize, maxMinSteps);
if (minStep == 0.0)
return;
15 years ago
// # ticks per interval
int numTicks = (int)::ceil(qwtAbs(stepSize / minStep)) - 1;
15 years ago
// Do the minor steps fit into the interval?
if ( QwtScaleArithmetic::compareEps((numTicks + 1) * qwtAbs(minStep),
qwtAbs(stepSize), stepSize) > 0) {
15 years ago
numTicks = 1;
minStep = stepSize * 0.5;
}
int medIndex = -1;
if ( numTicks % 2 )
medIndex = numTicks / 2;
// calculate minor ticks
for (int i = 0; i < (int)majorTicks.count(); i++) {
15 years ago
double val = majorTicks[i];
for (int k = 0; k < numTicks; k++) {
15 years ago
val += minStep;
double alignedValue = val;
if (QwtScaleArithmetic::compareEps(val, 0.0, stepSize) == 0)
15 years ago
alignedValue = 0.0;
if ( k == medIndex )
mediumTicks += alignedValue;
else
minorTicks += alignedValue;
}
}
}
/*!
\brief Align an interval to a step size
The limits of an interval are aligned that both are integer
multiples of the step size.
\param interval Interval
\param stepSize Step size
\return Aligned interval
*/
QwtDoubleInterval QwtLinearScaleEngine::align(
const QwtDoubleInterval &interval, double stepSize) const
{
const double x1 =
15 years ago
QwtScaleArithmetic::floorEps(interval.minValue(), stepSize);
const double x2 =
15 years ago
QwtScaleArithmetic::ceilEps(interval.maxValue(), stepSize);
return QwtDoubleInterval(x1, x2);
}
/*!
Return a transformation, for logarithmic (base 10) scales
*/
QwtScaleTransformation *QwtLog10ScaleEngine::transformation() const
{
return new QwtScaleTransformation(QwtScaleTransformation::Log10);
}
/*!
Align and divide an interval
\param maxNumSteps Max. number of steps
\param x1 First limit of the interval (In/Out)
\param x2 Second limit of the interval (In/Out)
\param stepSize Step size (Out)
\sa QwtScaleEngine::setAttribute
*/
void QwtLog10ScaleEngine::autoScale(int maxNumSteps,
double &x1, double &x2, double &stepSize) const
15 years ago
{
if ( x1 > x2 )
qSwap(x1, x2);
QwtDoubleInterval interval(x1 / pow(10.0, loMargin()),
x2 * pow(10.0, hiMargin()) );
15 years ago
double logRef = 1.0;
if (reference() > LOG_MIN / 2)
logRef = qwtMin(reference(), LOG_MAX / 2);
if (testAttribute(QwtScaleEngine::Symmetric)) {
const double delta = qwtMax(interval.maxValue() / logRef,
logRef / interval.minValue());
15 years ago
interval.setInterval(logRef / delta, logRef * delta);
}
if (testAttribute(QwtScaleEngine::IncludeReference))
interval = interval.extend(logRef);
interval = interval.limited(LOG_MIN, LOG_MAX);
if (interval.width() == 0.0)
interval = buildInterval(interval.minValue());
stepSize = divideInterval(log10(interval).width(), qwtMax(maxNumSteps, 1));
if ( stepSize < 1.0 )
stepSize = 1.0;
if (!testAttribute(QwtScaleEngine::Floating))
interval = align(interval, stepSize);
x1 = interval.minValue();
x2 = interval.maxValue();
if (testAttribute(QwtScaleEngine::Inverted)) {
15 years ago
qSwap(x1, x2);
stepSize = -stepSize;
}
}
/*!
\brief Calculate a scale division
\param x1 First interval limit
\param x2 Second interval limit
15 years ago
\param maxMajSteps Maximum for the number of major steps
\param maxMinSteps Maximum number of minor steps
\param stepSize Step size. If stepSize == 0, the scaleEngine
calculates one.
\sa QwtScaleEngine::stepSize, QwtLog10ScaleEngine::subDivide
*/
QwtScaleDiv QwtLog10ScaleEngine::divideScale(double x1, double x2,
int maxMajSteps, int maxMinSteps, double stepSize) const
15 years ago
{
QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized();
interval = interval.limited(LOG_MIN, LOG_MAX);
if (interval.width() <= 0 )
return QwtScaleDiv();
if (interval.maxValue() / interval.minValue() < 10.0) {
15 years ago
// scale width is less than one decade -> build linear scale
15 years ago
QwtLinearScaleEngine linearScaler;
linearScaler.setAttributes(attributes());
linearScaler.setReference(reference());
linearScaler.setMargins(loMargin(), hiMargin());
return linearScaler.divideScale(x1, x2,
maxMajSteps, maxMinSteps, stepSize);
15 years ago
}
stepSize = qwtAbs(stepSize);
if ( stepSize == 0.0 ) {
15 years ago
if ( maxMajSteps < 1 )
maxMajSteps = 1;
stepSize = divideInterval(log10(interval).width(), maxMajSteps);
if ( stepSize < 1.0 )
stepSize = 1.0; // major step must be >= 1 decade
}
QwtScaleDiv scaleDiv;
if ( stepSize != 0.0 ) {
15 years ago
QwtValueList ticks[QwtScaleDiv::NTickTypes];
buildTicks(interval, stepSize, maxMinSteps, ticks);
scaleDiv = QwtScaleDiv(interval, ticks);
}
if ( x1 > x2 )
scaleDiv.invert();
return scaleDiv;
}
void QwtLog10ScaleEngine::buildTicks(
const QwtDoubleInterval& interval, double stepSize, int maxMinSteps,
QwtValueList ticks[QwtScaleDiv::NTickTypes]) const
{
const QwtDoubleInterval boundingInterval =
align(interval, stepSize);
ticks[QwtScaleDiv::MajorTick] =
15 years ago
buildMajorTicks(boundingInterval, stepSize);
if ( maxMinSteps > 0 ) {
15 years ago
ticks[QwtScaleDiv::MinorTick] = buildMinorTicks(
ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize);
15 years ago
}
15 years ago
for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
ticks[i] = strip(ticks[i], interval);
}
QwtValueList QwtLog10ScaleEngine::buildMajorTicks(
const QwtDoubleInterval &interval, double stepSize) const
{
double width = log10(interval).width();
int numTicks = qRound(width / stepSize) + 1;
if ( numTicks > 10000 )
numTicks = 10000;
const double lxmin = log(interval.minValue());
const double lxmax = log(interval.maxValue());
const double lstep = (lxmax - lxmin) / double(numTicks - 1);
QwtValueList ticks;
ticks += interval.minValue();
for (int i = 1; i < numTicks; i++)
ticks += exp(lxmin + double(i) * lstep);
15 years ago
ticks += interval.maxValue();
return ticks;
}
QwtValueList QwtLog10ScaleEngine::buildMinorTicks(
const QwtValueList &majorTicks,
15 years ago
int maxMinSteps, double stepSize) const
{
if (stepSize < 1.1) { // major step width is one decade
15 years ago
if ( maxMinSteps < 1 )
return QwtValueList();
15 years ago
int k0, kstep, kmax;
if (maxMinSteps >= 8) {
15 years ago
k0 = 2;
kmax = 9;
kstep = 1;
} else if (maxMinSteps >= 4) {
15 years ago
k0 = 2;
kmax = 8;
kstep = 2;
} else if (maxMinSteps >= 2) {
15 years ago
k0 = 2;
kmax = 5;
kstep = 3;
} else {
15 years ago
k0 = 5;
kmax = 5;
kstep = 1;
}
QwtValueList minorTicks;
for (int i = 0; i < (int)majorTicks.count(); i++) {
15 years ago
const double v = majorTicks[i];
for (int k = k0; k<= kmax; k+=kstep)
minorTicks += v * double(k);
}
return minorTicks;
} else { // major step > one decade
15 years ago
double minStep = divideInterval(stepSize, maxMinSteps);
if ( minStep == 0.0 )
return QwtValueList();
if ( minStep < 1.0 )
minStep = 1.0;
// # subticks per interval
int nMin = qRound(stepSize / minStep) - 1;
// Do the minor steps fit into the interval?
if ( QwtScaleArithmetic::compareEps((nMin + 1) * minStep,
qwtAbs(stepSize), stepSize) > 0) {
15 years ago
nMin = 0;
}
if (nMin < 1)
return QwtValueList(); // no subticks
// substep factor = 10^substeps
const double minFactor = qwtMax(pow(10.0, minStep), 10.0);
QwtValueList minorTicks;
for (int i = 0; i < (int)majorTicks.count(); i++) {
15 years ago
double val = majorTicks[i];
for (int k=0; k< nMin; k++) {
15 years ago
val *= minFactor;
minorTicks += val;
}
}
return minorTicks;
}
}
/*!
\brief Align an interval to a step size
The limits of an interval are aligned that both are integer
multiples of the step size.
\param interval Interval
\param stepSize Step size
\return Aligned interval
*/
QwtDoubleInterval QwtLog10ScaleEngine::align(
const QwtDoubleInterval &interval, double stepSize) const
{
const QwtDoubleInterval intv = log10(interval);
const double x1 = QwtScaleArithmetic::floorEps(intv.minValue(), stepSize);
const double x2 = QwtScaleArithmetic::ceilEps(intv.maxValue(), stepSize);
return pow10(QwtDoubleInterval(x1, x2));
}
/*!
Return the interval [log10(interval.minValue(), log10(interval.maxValue]
*/
QwtDoubleInterval QwtLog10ScaleEngine::log10(
const QwtDoubleInterval &interval) const
{
return QwtDoubleInterval(::log10(interval.minValue()),
::log10(interval.maxValue()));
15 years ago
}
/*!
Return the interval [pow10(interval.minValue(), pow10(interval.maxValue]
*/
QwtDoubleInterval QwtLog10ScaleEngine::pow10(
const QwtDoubleInterval &interval) const
{
return QwtDoubleInterval(pow(10.0, interval.minValue()),
pow(10.0, interval.maxValue()));
15 years ago
}