From 1bee23cf4f6fa545c740a52299b34aa5e1b58032 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 12 Jun 2020 12:38:35 +0200 Subject: [PATCH] [apps/graph] Reworked function caching Instead of being memoized all at once before display, functions values are now stored at evaluation time. Fixed some quirks with caching preparation. Change-Id: I5d212c271c8c41a6dc9074a15c720f0bccf8ac40 --- apps/graph/continuous_function_store.h | 4 +- apps/graph/graph/graph_view.cpp | 21 ++-- apps/shared/continuous_function.cpp | 12 --- apps/shared/continuous_function.h | 8 +- apps/shared/continuous_function_cache.cpp | 122 +++++++++------------- apps/shared/continuous_function_cache.h | 17 ++- apps/shared/curve_view.cpp | 7 +- apps/shared/curve_view.h | 5 +- 8 files changed, 84 insertions(+), 112 deletions(-) diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h index eb506822b..1789bc682 100644 --- a/apps/graph/continuous_function_store.h +++ b/apps/graph/continuous_function_store.h @@ -16,7 +16,7 @@ public: return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); } Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } - Shared::ExpiringPointer cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? Shared::ExpiringPointer(k_functionCaches + i) : nullptr; } + Shared::ContinuousFunctionCache * cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? m_functionCaches + i : nullptr; } Ion::Storage::Record::ErrorStatus addEmptyModel() override; private: const char * modelExtension() const override { return Ion::Storage::funcExtension; } @@ -27,7 +27,7 @@ private: return isFunctionActive(model, context) && plotType == static_cast(model)->plotType(); } mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; - mutable Shared::ContinuousFunctionCache k_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; + mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; }; diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 226838e91..ac22f7c65 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -1,6 +1,7 @@ #include "graph_view.h" #include "../app.h" #include +#include using namespace Shared; @@ -28,7 +29,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { for (int i = 0; i < activeFunctionsCount ; i++) { Ion::Storage::Record record = functionStore->activeRecordAtIndex(i); ExpiringPointer f = functionStore->modelForRecord(record); - ExpiringPointer cch = functionStore->cacheAtIndex(i); + ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i); Shared::ContinuousFunction::PlotType type = f->plotType(); Poincare::Expression e = f->expressionReduced(context()); if (e.isUndefined() || ( @@ -52,6 +53,17 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { * how fast the function moves... */ float tstep = (tmax-tmin)/10.0938275501223f; + float tCacheMin, tCacheStep; + if (type == ContinuousFunction::PlotType::Cartesian) { + float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft); + tCacheStep = pixelWidth(); + } else { + tCacheMin = tmin; + tCacheStep = tstep; + } + ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); + // Cartesian if (type == Shared::ContinuousFunction::PlotType::Cartesian) { drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { @@ -63,8 +75,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, - &ContinuousFunctionCache::PrepareCache, cch.operator->()); + }); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context()); @@ -85,9 +96,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), false, f->color(), - true, false, 0.0f, 0.0f, /* drawCurve's default arguments */ - &ContinuousFunctionCache::PrepareCache, cch.operator->()); + }, f.operator->(), context(), false, f->color()); } } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 59a9b43d7..885f5f2e7 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -351,18 +351,6 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e * the derivative table. */ } -Poincare::Coordinate2D ContinuousFunction::checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const { - Poincare::Coordinate2D res(NAN, NAN); - if (cacheIsFilled()) { - res = cache()->valueForParameter(this, t); - } - if (std::isnan(res.x1()) || std::isnan(res.x2())) { - res = privateEvaluateXYAtParameter(t, context); - //res = Poincare::Coordinate2D(privateEvaluateXYAtParameter(t, context).x1(), 0); - } - return res; -} - Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { clearCache(); return ExpressionModelHandle::setContent(c, context); diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 284e967b9..0874941a4 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -26,7 +26,8 @@ public: static void DefaultName(char buffer[], size_t bufferSize); static ContinuousFunction NewModel(Ion::Storage::Record::ErrorStatus * error, const char * baseName = nullptr); ContinuousFunction(Ion::Storage::Record record = Record()) : - Function(record) + Function(record), + m_cache(nullptr) {} I18n::Message parameterMessageName() const override; CodePoint symbol() const override; @@ -47,8 +48,7 @@ public: return templatedApproximateAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(float t, Poincare::Context * context) const override { - //return privateEvaluateXYAtParameter(t, context); - return checkForCacheHitAndEvaluate(t, context); + return (m_cache) ? m_cache->valueForParameter(this, context, t) : privateEvaluateXYAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(double t, Poincare::Context * context) const override { return privateEvaluateXYAtParameter(t, context); @@ -83,14 +83,12 @@ public: ContinuousFunctionCache * cache() const { return m_cache; } void setCache(ContinuousFunctionCache * v) { m_cache = v; } void clearCache() { m_cache = nullptr; } - bool cacheIsFilled() const { return cache() && cache()->filled(); } Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) override; private: constexpr static float k_polarParamRangeSearchNumberOfPoints = 100.0f; // This is ad hoc, no special justification typedef Poincare::Coordinate2D (*ComputePointOfInterest)(Poincare::Expression e, char * symbol, double start, double step, double max, Poincare::Context * context); Poincare::Coordinate2D nextPointOfInterestFrom(double start, double step, double max, Poincare::Context * context, ComputePointOfInterest compute) const; template Poincare::Coordinate2D privateEvaluateXYAtParameter(T t, Poincare::Context * context) const; - Poincare::Coordinate2D checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const; /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on * Shared::Function::RecordDataBuffer about packing. */ diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index ce8c08737..464186226 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -1,5 +1,6 @@ #include "continuous_function_cache.h" #include "continuous_function.h" +#include namespace Shared { @@ -8,43 +9,38 @@ constexpr float ContinuousFunctionCache::k_cacheHitTolerance; constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; // public -void ContinuousFunctionCache::PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep) { - if (!cch) { +void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { + if (!cache) { + /* ContinuousFunctionStore::cacheAtIndex has returned a nullptr : the index + * of the function we are trying to draw is greater than the number of + * available caches, so we do nothing.*/ return; } - ContinuousFunction * function = (ContinuousFunction *)f; - Poincare::Context * context = (Poincare::Context *)ctx; - if (!function->cache()) { - ContinuousFunctionCache * cache = (ContinuousFunctionCache *)cch; + + ContinuousFunction * function = static_cast(fun); + if (function->cache() != cache) { cache->clear(); function->setCache(cache); } - if (function->cache()->filled() && tStep / StepFactor(function) == function->cache()->step()) { - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - function->cache()->pan(function, context, tMin); - } - return; + + if (function->plotType() == ContinuousFunction::PlotType::Cartesian && tStep != 0) { + function->cache()->pan(function, tMin); } function->cache()->setRange(function, tMin, tStep); - function->cache()->memoize(function, context); } void ContinuousFunctionCache::clear() { - m_filled = false; m_startOfCache = 0; + m_tStep = 0; + invalidateBetween(0, k_sizeOfCache); } -Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, float t) const { - int iRes = indexForParameter(function, t); - /* If t does not map to an index, iRes is -1 */ - if (iRes < 0) { - return Poincare::Coordinate2D(NAN, NAN); +Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t) { + int resIndex = indexForParameter(function, t); + if (resIndex < 0) { + return function->privateEvaluateXYAtParameter(t, context); } - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - return Poincare::Coordinate2D(t, m_cache[iRes]); - } - assert(m_startOfCache == 0); - return Poincare::Coordinate2D(m_cache[2*iRes], m_cache[2*iRes+1]); + return valuesAtIndex(function, context, t, resIndex); } // private @@ -55,48 +51,17 @@ float ContinuousFunctionCache::StepFactor(ContinuousFunction * function) { return (function->plotType() == ContinuousFunction::PlotType::Cartesian) ? 1.f : 16.f; } +void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { + for (int i = iInf; i < iSup; i++) { + m_cache[i] = NAN; + } +} + void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { m_tMin = tMin; m_tStep = tStep / StepFactor(function); } -void ContinuousFunctionCache::memoize(ContinuousFunction * function, Poincare::Context * context) { - m_filled = true; - m_startOfCache = 0; - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - memoizeYForX(function, context); - return; - } - memoizeXYForT(function, context); -} - -void ContinuousFunctionCache::memoizeYForX(ContinuousFunction * function, Poincare::Context * context) { - memoizeYForXBetweenIndices(function, context, 0, k_sizeOfCache); -} - -void ContinuousFunctionCache::memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup) { - assert(function->plotType() == ContinuousFunction::PlotType::Cartesian); - for (int i = iInf; i < iSup; i++) { - m_cache[i] = function->privateEvaluateXYAtParameter(parameterForIndex(i), context).x2(); - } -} - -void ContinuousFunctionCache::memoizeXYForT(ContinuousFunction * function, Poincare::Context * context) { - assert(function->plotType() != ContinuousFunction::PlotType::Cartesian); - for (int i = 1; i < k_sizeOfCache; i += 2) { - Poincare::Coordinate2D res = function->privateEvaluateXYAtParameter(parameterForIndex(i/2), context); - m_cache[i - 1] = res.x1(); - m_cache[i] = res.x2(); - } -} - -float ContinuousFunctionCache::parameterForIndex(int i) const { - if (i < m_startOfCache) { - i += k_sizeOfCache; - } - return m_tMin + m_tStep * (i - m_startOfCache); -} - int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * function, float t) const { float delta = (t - m_tMin) / m_tStep; if (delta < 0 || delta > INT_MAX) { @@ -104,14 +69,31 @@ int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * functi } int res = std::round(delta); assert(res >= 0); - if (res >= k_sizeOfCache || std::abs(res - delta) > k_cacheHitTolerance) { + if ((res >= k_sizeOfCache && function->plotType() == ContinuousFunction::PlotType::Cartesian) + || (res >= k_sizeOfCache / 2 && function->plotType() != ContinuousFunction::PlotType::Cartesian) + || std::abs(res - delta) > k_cacheHitTolerance) { return -1; } assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0); return (res + m_startOfCache) % k_sizeOfCache; } -void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Context * context, float newTMin) { +Poincare::Coordinate2D ContinuousFunctionCache::valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i) { + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + if (std::isnan(m_cache[i])) { + m_cache[i] = function->privateEvaluateXYAtParameter(t, context).x2(); + } + return Poincare::Coordinate2D(t, m_cache[i]); + } + if (std::isnan(m_cache[2 * i]) || std::isnan(m_cache[2 * i + 1])) { + Poincare::Coordinate2D res = function->privateEvaluateXYAtParameter(t, context); + m_cache[2 * i] = res.x1(); + m_cache[2 * i + 1] = res.x2(); + } + return Poincare::Coordinate2D(m_cache[2 * i], m_cache[2 * i + 1]); +} + +void ContinuousFunctionCache::pan(ContinuousFunction * function, float newTMin) { assert(function->plotType() == ContinuousFunction::PlotType::Cartesian); if (newTMin == m_tMin) { return; @@ -120,12 +102,12 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte float dT = (newTMin - m_tMin) / m_tStep; m_tMin = newTMin; if (std::abs(dT) > INT_MAX) { - memoize(function, context); + clear(); return; } int dI = std::round(dT); if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::abs(dT - dI) > k_cacheHitTolerance) { - memoize(function, context); + clear(); return; } @@ -136,17 +118,17 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte } if (dI > 0) { if (m_startOfCache > oldStart) { - memoizeYForXBetweenIndices(function, context, oldStart, m_startOfCache); + invalidateBetween(oldStart, m_startOfCache); } else { - memoizeYForXBetweenIndices(function, context, oldStart, k_sizeOfCache); - memoizeYForXBetweenIndices(function, context, 0, m_startOfCache); + invalidateBetween(oldStart, k_sizeOfCache); + invalidateBetween(0, m_startOfCache); } } else { if (m_startOfCache > oldStart) { - memoizeYForXBetweenIndices(function, context, m_startOfCache, k_sizeOfCache); - memoizeYForXBetweenIndices(function, context, 0, oldStart); + invalidateBetween(m_startOfCache, k_sizeOfCache); + invalidateBetween(0, oldStart); } else { - memoizeYForXBetweenIndices(function, context, m_startOfCache, oldStart); + invalidateBetween(m_startOfCache, oldStart); } } } diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index 046357e71..b48617214 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -15,12 +15,13 @@ public: * function */ static constexpr int k_numberOfAvailableCaches = 2; - static void PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep); + static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); + + ContinuousFunctionCache() { clear(); } float step() const { return m_tStep; } - bool filled() const { return m_filled; } void clear(); - Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, float t) const; + Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t); private: /* The size of the cache is chosen to optimize the display of cartesian * function */ @@ -29,14 +30,11 @@ private: static float StepFactor(ContinuousFunction * function); + void invalidateBetween(int iInf, int iSup); void setRange(ContinuousFunction * function, float tMin, float tStep); - void memoize(ContinuousFunction * function, Poincare::Context * context); - void memoizeYForX(ContinuousFunction * function, Poincare::Context * context); - void memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup); - void memoizeXYForT(ContinuousFunction * function, Poincare::Context * context); - float parameterForIndex(int i) const; int indexForParameter(const ContinuousFunction * function, float t) const; - void pan(ContinuousFunction * function, Poincare::Context * context, float newTMin); + Poincare::Coordinate2D valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i); + void pan(ContinuousFunction * function, float newTMin); float m_tMin, m_tStep; float m_cache[k_sizeOfCache]; @@ -44,7 +42,6 @@ private: * with cartesian functions. When dealing with parametric or polar functions, * m_startOfCache should be zero.*/ int m_startOfCache; - bool m_filled; }; } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 239ea34d9..77003ab40 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -602,8 +602,7 @@ const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { constexpr static int k_maxNumberOfIterations = 10; -void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation, PrepareContinuousFunction functionPreparator, void * cache) const { - functionPreparator(model, context, cache, tStart, tStep); +void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const { float previousT = NAN; float t = NAN; float previousX = NAN; @@ -635,7 +634,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd } while (true); } -void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation, PrepareContinuousFunction functionPreparator, void * cache) const { +void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); float tStart = std::isnan(rectLeft) ? xMin : std::max(xMin, rectLeft); @@ -645,7 +644,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo return; } float tStep = pixelWidth(); - drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator, cache); + drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 280ed488c..00a3119a0 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -19,7 +19,6 @@ public: typedef Poincare::Coordinate2D (*EvaluateXYForFloatParameter)(float t, void * model, void * context); typedef Poincare::Coordinate2D (*EvaluateXYForDoubleParameter)(double t, void * model, void * context); typedef float (*EvaluateYForX)(float x, void * model, void * context); - typedef void (* PrepareContinuousFunction)(void * model, void * context, void * cache, float xMin, float xStep); enum class Axis { Horizontal = 0, Vertical = 1 @@ -108,8 +107,8 @@ protected: void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; - void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr,PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = nullptr) const; - void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr, PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = nullptr) const; + void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; + void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; void drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const; void computeLabels(Axis axis);