diff --git a/profile-widget/divecartesianaxis.cpp b/profile-widget/divecartesianaxis.cpp index 1c154bd0d..4689b9278 100644 --- a/profile-widget/divecartesianaxis.cpp +++ b/profile-widget/divecartesianaxis.cpp @@ -22,6 +22,7 @@ DiveCartesianAxis::DiveCartesianAxis(Position position, int integralDigits, int double labelScale, bool printMode, bool isGrayscale, ProfileScene &scene) : printMode(printMode), position(position), + fractionalDigits(fractionalDigits), gridColor(gridColor), scene(scene), orientation(LeftToRight), @@ -32,7 +33,8 @@ DiveCartesianAxis::DiveCartesianAxis(Position position, int integralDigits, int lineVisibility(true), labelScale(labelScale), changed(true), - dpr(dpr) + dpr(dpr), + transform({1.0, 0.0}) { QPen pen; pen.setColor(getColor(TIME_GRID, isGrayscale)); @@ -77,6 +79,13 @@ void DiveCartesianAxis::setOrientation(Orientation o) changed = true; } +void DiveCartesianAxis::setTransform(double a, double b) +{ + transform.a = a; + transform.b = b; + changed = true; +} + QColor DiveCartesianAxis::colorForValue(double) const { return QColor(Qt::black); @@ -282,9 +291,19 @@ void DiveCartesianAxis::animateChangeLine(const QRectF &rectIn, int animSpeed) sizeChanged(); } +double DiveCartesianAxis::Transform::to(double x) const +{ + return a*x + b; +} + +double DiveCartesianAxis::Transform::from(double y) const +{ + return (y - b) / a; +} + QString DiveCartesianAxis::textForValue(double value) const { - return QString("%L1").arg(value, 0, 'g', 4); + return QStringLiteral("%L1").arg(transform.to(value), 0, 'f', fractionalDigits); } void DiveCartesianAxis::setTickInterval(double i) @@ -353,13 +372,6 @@ std::pair DiveCartesianAxis::screenMinMax() const : std::make_pair(rect.top(), rect.bottom()); } -QString DepthAxis::textForValue(double value) const -{ - if (value == 0) - return QString(); - return get_depth_string(lrint(value), false, false); -} - QColor DepthAxis::colorForValue(double) const { return QColor(Qt::red); @@ -389,11 +401,6 @@ void TimeAxis::updateTicks(int animSpeed) } } -QString TemperatureAxis::textForValue(double value) const -{ - return QString::number(mkelvin_to_C((int)value)); -} - PartialGasPressureAxis::PartialGasPressureAxis(const DivePlotDataModel &model, Position position, int integralDigits, int fractionalDigits, color_index_t gridColor, double dpr, double labelScale, bool printMode, bool isGrayscale, ProfileScene &scene) : diff --git a/profile-widget/divecartesianaxis.h b/profile-widget/divecartesianaxis.h index 8b4fd3a03..55cd32ac8 100644 --- a/profile-widget/divecartesianaxis.h +++ b/profile-widget/divecartesianaxis.h @@ -32,10 +32,12 @@ public: enum class Position { Left, Right, Bottom }; + DiveCartesianAxis(Position position, int integralDigits, int fractionalDigits, color_index_t gridColor, double dpr, double labelScale, bool printMode, bool isGrayscale, ProfileScene &scene); ~DiveCartesianAxis(); void setBounds(double min, double max); + void setTransform(double a, double b = 0.0); void setTickInterval(double interval); void setOrientation(Orientation orientation); double minimum() const; @@ -56,6 +58,7 @@ signals: protected: Position position; + int fractionalDigits; QRectF rect; // Rectangle to fill with grid lines QPen gridPen; color_index_t gridColor; @@ -74,6 +77,16 @@ protected: bool changed; double dpr; double labelWidth, labelHeight; // maximum expected sizes of label width and height + + // To format the labels and choose the label positions, the + // axis has to be aware of the displayed values. Thankfully, + // the conversion between internal data (eg. mm) and displayed + // data (e.g. ft) can be represented by an affine map ax+b. + struct Transform { + double a, b; + double to(double x) const; + double from(double y) const; + } transform; }; class DepthAxis : public DiveCartesianAxis { @@ -81,7 +94,6 @@ class DepthAxis : public DiveCartesianAxis { public: using DiveCartesianAxis::DiveCartesianAxis; private: - QString textForValue(double value) const override; QColor colorForValue(double value) const override; }; @@ -99,8 +111,6 @@ class TemperatureAxis : public DiveCartesianAxis { Q_OBJECT public: using DiveCartesianAxis::DiveCartesianAxis; -private: - QString textForValue(double value) const override; }; class PartialGasPressureAxis : public DiveCartesianAxis { diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index 020930f17..c508e61a5 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -51,7 +51,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) : gasYAxis(new PartialGasPressureAxis(*dataModel, DiveCartesianAxis::Position::Right, 1, 2, TIME_GRID, dpr, 0.7, printMode, isGrayscale, *this)), temperatureAxis(new TemperatureAxis(DiveCartesianAxis::Position::Right, 3, 0, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)), timeAxis(new TimeAxis(DiveCartesianAxis::Position::Bottom, 2, 2, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)), - cylinderPressureAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 2, 2, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)), + cylinderPressureAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 4, 0, TIME_GRID, dpr, 1.0, printMode, isGrayscale, *this)), heartBeatAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Left, 3, 0, HR_AXIS, dpr, 0.7, printMode, isGrayscale, *this)), percentageAxis(new DiveCartesianAxis(DiveCartesianAxis::Position::Right, 2, 0, TIME_GRID, dpr, 0.7, printMode, isGrayscale, *this)), diveProfileItem(createItem(*profileYAxis, DivePlotDataModel::DEPTH, 0, dpr)), @@ -115,6 +115,11 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) : gasYAxis->setZValue(timeAxis->zValue() + 1); tankItem->setZValue(100); + // These axes are not locale-dependent. Set their scale factor once here. + timeAxis->setTransform(60.0); + heartBeatAxis->setTransform(1.0); + gasYAxis->setTransform(1.0); // Non-metric countries likewise use bar (disguised as "percentage") for partial pressure. + for (int i = 0; i < 16; i++) { DiveCalculatedTissue *tissueItem = createItem(*profileYAxis, DivePlotDataModel::TISSUE_1 + i, i + 1, dpr); allTissues.append(tissueItem); @@ -199,6 +204,19 @@ void ProfileScene::resize(QSizeF size) setSceneRect(QRectF(QPointF(), size)); } +// Helper templates to determine slope and intersect of a linear function. +// The function arguments are supposed to be integral types. +template +static auto intercept(Func f) +{ + return f(0); +} +template +static auto slope(Func f) +{ + return f(1) - f(0); +} + // Helper structure for laying out secondary plots. struct VerticalAxisLayout { DiveCartesianAxis *axis; @@ -286,6 +304,16 @@ void ProfileScene::updateAxes(bool instant, bool diveHasHeartBeat) // The cylinders are displayed in the 24-80% region of the profile cylinderPressureAxis->animateChangeLine(QRectF(leftBorder, topBorder + 0.24 * height, width, 0.56 * height), animSpeed); + + // Set scale factors depending on locale. + // The conversion calls, such as mm_to_feet(), will be optimized away. + profileYAxis->setTransform(prefs.units.length == units::METERS ? 0.001 : slope(mm_to_feet)); + cylinderPressureAxis->setTransform(prefs.units.pressure == units::BAR ? 0.001 : slope(mbar_to_PSI)); + // Temperature is special: this is not a linear transformation, but requires a shift of origin. + if (prefs.units.temperature == units::CELSIUS) + temperatureAxis->setTransform(slope(mkelvin_to_C), intercept(mkelvin_to_C)); + else + temperatureAxis->setTransform(slope(mkelvin_to_F), intercept(mkelvin_to_F)); } bool ProfileScene::isPointOutOfBoundaries(const QPointF &point) const