From 9e12b61849d56709da05b6d9903b737fbfe53992 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 9 Jul 2020 10:14:59 +0200 Subject: [PATCH] [apps/graph] Round x before evaluating graph cursor on scroll Change-Id: I13500669963eb8130e188a898bed0bf63655add6 --- apps/graph/graph/graph_controller_helper.cpp | 7 +++-- apps/shared/function_banner_delegate.cpp | 30 ++++++++++++++++--- apps/shared/function_banner_delegate.h | 8 +++++ .../function_go_to_parameter_controller.cpp | 5 ++++ .../interactive_curve_view_controller.cpp | 5 ++++ poincare/src/print_float.cpp | 2 +- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index 90785d29a..249ef8bd1 100644 --- a/apps/graph/graph/graph_controller_helper.cpp +++ b/apps/graph/graph/graph_controller_helper.cpp @@ -27,8 +27,11 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer double dir = (direction > 0 ? 1.0 : -1.0); double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor; - step *= scrollSpeed; - t += dir * step; + t += dir * step * scrollSpeed; + + // If possible, round t so that f(x) matches f evaluated at displayed x + t = FunctionBannerDelegate::getValueDisplayedOnBanner(t, App::app()->localContext(), 0.05 * step, true); + t = std::max(tMin, std::min(tMax, t)); Coordinate2D xy = function->evaluateXYAtParameter(t, App::app()->localContext()); cursor->moveTo(t, xy.x1(), xy.x2()); diff --git a/apps/shared/function_banner_delegate.cpp b/apps/shared/function_banner_delegate.cpp index 87e0f953b..29c3d4f3b 100644 --- a/apps/shared/function_banner_delegate.cpp +++ b/apps/shared/function_banner_delegate.cpp @@ -7,6 +7,12 @@ using namespace Poincare; namespace Shared { +constexpr int k_precision = Preferences::MediumNumberOfSignificantDigits; + +int convertDoubleToText(double t, char * buffer, int bufferSize) { + return PoincareHelpers::ConvertFloatToText(t, buffer, bufferSize, k_precision); +} + void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, Poincare::Context * context) { ExpiringPointer function = functionStore->modelForRecord(record); constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::charSizeForFloatsWithPrecision(Preferences::LargeNumberOfSignificantDigits); @@ -18,9 +24,7 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor strlcpy(buffer + numberOfChar, "=", bufferSize - numberOfChar); bannerView()->abscissaSymbol()->setText(buffer); - constexpr int precision = Preferences::MediumNumberOfSignificantDigits; - - numberOfChar = PoincareHelpers::ConvertFloatToText(cursor->t(), buffer, bufferSize, precision); + numberOfChar = convertDoubleToText(cursor->t(), buffer, bufferSize); assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar); bannerView()->abscissaValue()->setText(buffer); @@ -28,7 +32,7 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor numberOfChar = function->nameWithArgument(buffer, bufferSize); assert(numberOfChar <= bufferSize); numberOfChar += strlcpy(buffer+numberOfChar, "=", bufferSize-numberOfChar); - numberOfChar += function->printValue(cursor->t(), cursor->x(),cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, precision, context); + numberOfChar += function->printValue(cursor->t(), cursor->x(),cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, k_precision, context); assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar); bannerView()->ordinateView()->setText(buffer); @@ -36,4 +40,22 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor bannerView()->reload(); } +double FunctionBannerDelegate::getValueDisplayedOnBanner(double t, Poincare::Context * context, double deltaThreshold, bool roundToZero) { + if (roundToZero && std::fabs(t) < deltaThreshold) { + // Round to 0 to avoid rounding to unnecessary low non-zero value. + return 0.0; + } + // Convert float to text + constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::charSizeForFloatsWithPrecision(k_precision); + char buffer[bufferSize]; + int numberOfChar = convertDoubleToText(t, buffer, bufferSize); + assert(numberOfChar <= bufferSize); + // Silence compiler warnings + (void) numberOfChar; + // Extract displayed value + double displayedValue = PoincareHelpers::ApproximateToScalar(buffer, context); + // Return displayed value if difference from t is under deltaThreshold + return std::fabs(displayedValue-t) < deltaThreshold ? displayedValue : t; +} + } diff --git a/apps/shared/function_banner_delegate.h b/apps/shared/function_banner_delegate.h index f02a1f000..30acdcf1f 100644 --- a/apps/shared/function_banner_delegate.h +++ b/apps/shared/function_banner_delegate.h @@ -10,6 +10,14 @@ namespace Shared { class FunctionBannerDelegate { public: constexpr static int k_maxNumberOfCharacters = 50; + /* getValueDisplayedOnBanner returns the value of t as displayed in the + * banner, unless the difference from t exceeds deltaThreshold. If so, + * return t. For instance, when a function is plotted between 1.000001 and + * 1.000003, and the user goes to x = 1.000002, a small deltaThreshold + * prevents him from being sent to x = 1 + * Note : Due to double encoding, not all values of t can be properly rounded. + * For instance, x displayed as 0.01 can at best be encoded to x=0.010...02 */ + static double getValueDisplayedOnBanner(double t, Poincare::Context * context, double deltaThreshold, bool roundToZero = false); protected: void reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, Poincare::Context * context); virtual XYBannerView * bannerView() = 0; diff --git a/apps/shared/function_go_to_parameter_controller.cpp b/apps/shared/function_go_to_parameter_controller.cpp index c5b21cb43..bcb827923 100644 --- a/apps/shared/function_go_to_parameter_controller.cpp +++ b/apps/shared/function_go_to_parameter_controller.cpp @@ -2,6 +2,7 @@ #include "function_app.h" #include #include +#include namespace Shared { @@ -15,6 +16,10 @@ bool FunctionGoToParameterController::confirmParameterAtIndex(int parameterIndex assert(parameterIndex == 0); FunctionApp * myApp = FunctionApp::app(); ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); + // If possible, round f so that we go to the evaluation of the displayed f + double pixelWidth = (m_graphRange->xMax() - m_graphRange->xMin()) / Ion::Display::Width; + f = FunctionBannerDelegate::getValueDisplayedOnBanner(f, myApp->localContext(), pixelWidth, false); + Poincare::Coordinate2D xy = function->evaluateXYAtParameter(f, myApp->localContext()); m_cursor->moveTo(f, xy.x1(), xy.x2()); m_graphRange->centerAxisAround(CurveViewRange::Axis::X, m_cursor->x()); diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 25aa9149d..9e4c2ebed 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -1,3 +1,4 @@ +#include "function_banner_delegate.h" #include "interactive_curve_view_controller.h" #include #include @@ -219,6 +220,10 @@ bool InteractiveCurveViewController::textFieldDidFinishEditing(TextField * textF if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { return false; } + /* If possible, round floatBody so that we go to the evaluation of the + * displayed floatBody */ + floatBody = FunctionBannerDelegate::getValueDisplayedOnBanner(floatBody, textFieldDelegateApp()->localContext(), curveView()->pixelWidth(), false); + Coordinate2D xy = xyValues(selectedCurveIndex(), floatBody, textFieldDelegateApp()->localContext()); m_cursor->moveTo(floatBody, xy.x1(), xy.x2()); interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index d782302de..d9147a275 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -229,7 +229,7 @@ PrintFloat::TextLengths PrintFloat::ConvertFloatToTextPrivate(T f, char * buffer * function. */ if (std::isnan(mantissa) || std::isinf(mantissa)) { mantissa = std::round(std::pow(10, std::log10(std::fabs(f))+(T)(numberOfSignificantDigits -1 - exponentInBase10))); - mantissa = std::copysign(mantissa, f); + mantissa = std::copysign(mantissa, static_cast(f)); } /* We update the exponent in base 10 (if 0.99999999 was rounded to 1 for * instance)