[apps/graph] Round x before evaluating graph cursor on scroll

Change-Id: I13500669963eb8130e188a898bed0bf63655add6
This commit is contained in:
Hugo Saint-Vignes
2020-07-09 10:14:59 +02:00
committed by Émilie Feral
parent bd2609bcba
commit 9e12b61849
6 changed files with 50 additions and 7 deletions

View File

@@ -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<double> xy = function->evaluateXYAtParameter(t, App::app()->localContext());
cursor->moveTo(t, xy.x1(), xy.x2());

View File

@@ -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<double>(t, buffer, bufferSize, k_precision);
}
void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, Poincare::Context * context) {
ExpiringPointer<Function> 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<double>(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<double>(buffer, context);
// Return displayed value if difference from t is under deltaThreshold
return std::fabs(displayedValue-t) < deltaThreshold ? displayedValue : t;
}
}

View File

@@ -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;

View File

@@ -2,6 +2,7 @@
#include "function_app.h"
#include <assert.h>
#include <cmath>
#include <ion/display.h>
namespace Shared {
@@ -15,6 +16,10 @@ bool FunctionGoToParameterController::confirmParameterAtIndex(int parameterIndex
assert(parameterIndex == 0);
FunctionApp * myApp = FunctionApp::app();
ExpiringPointer<Function> 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<double> xy = function->evaluateXYAtParameter(f, myApp->localContext());
m_cursor->moveTo(f, xy.x1(), xy.x2());
m_graphRange->centerAxisAround(CurveViewRange::Axis::X, m_cursor->x());

View File

@@ -1,3 +1,4 @@
#include "function_banner_delegate.h"
#include "interactive_curve_view_controller.h"
#include <cmath>
#include <float.h>
@@ -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<double> 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());

View File

@@ -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<double>(f));
}
/* We update the exponent in base 10 (if 0.99999999 was rounded to 1 for
* instance)