mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-20 14:20:39 +01:00
224 lines
8.4 KiB
C++
224 lines
8.4 KiB
C++
#include "graph_view.h"
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <float.h>
|
|
|
|
namespace Graph {
|
|
|
|
constexpr KDColor GraphView::k_gridColor;
|
|
|
|
GraphView::GraphView(FunctionStore * functionStore, GraphWindow * graphWindow) :
|
|
CurveView(),
|
|
m_cursorView(CursorView()),
|
|
m_xCursorPosition(-1.0f),
|
|
m_yCursorPosition(-1.0f),
|
|
m_visibleCursor(true),
|
|
m_graphWindow(graphWindow),
|
|
m_functionStore(functionStore),
|
|
m_evaluateContext(nullptr)
|
|
{
|
|
}
|
|
|
|
int GraphView::numberOfSubviews() const {
|
|
return 1;
|
|
};
|
|
|
|
View * GraphView::subviewAtIndex(int index) {
|
|
assert(index == 0);
|
|
return &m_cursorView;
|
|
}
|
|
|
|
void GraphView::setContext(Context * context) {
|
|
m_evaluateContext = (EvaluateContext *)context;
|
|
}
|
|
|
|
Context * GraphView::context() const {
|
|
return m_evaluateContext;
|
|
}
|
|
|
|
int GraphView::indexFunctionSelectedByCursor() {
|
|
return m_indexFunctionSelectedByCursor;
|
|
}
|
|
|
|
void GraphView::reload() {
|
|
markRectAsDirty(bounds());
|
|
layoutSubviews();
|
|
computeLabels(Axis::Horizontal);
|
|
computeLabels(Axis::Vertical);
|
|
}
|
|
|
|
float GraphView::scale(Axis axis) const {
|
|
return (axis == Axis::Horizontal ? m_graphWindow->xScale() : m_graphWindow->yScale());
|
|
}
|
|
|
|
char * GraphView::label(Axis axis, int index) const {
|
|
return (axis == Axis::Horizontal ? (char *)m_xLabels[index] : (char *)m_yLabels[index]);
|
|
}
|
|
|
|
float GraphView::xPixelCursorPosition() {
|
|
return m_xCursorPosition;
|
|
}
|
|
|
|
float GraphView::xCursorPosition() {
|
|
return min(Axis::Horizontal) + m_xCursorPosition*(max(Axis::Horizontal)-min(Axis::Horizontal))/pixelLength(Axis::Horizontal);
|
|
}
|
|
|
|
void GraphView::setXCursorPosition(float xPosition, Function * function) {
|
|
float xRange = max(Axis::Horizontal) - min(Axis::Horizontal);
|
|
float yRange = max(Axis::Horizontal) - min(Axis::Horizontal);
|
|
m_graphWindow->setXMin(xPosition - xRange/2.0f);
|
|
m_graphWindow->setXMax(xPosition + xRange/2.0f);
|
|
m_xCursorPosition = floatToPixel(Axis::Horizontal, xPosition);
|
|
float yPosition = function->evaluateAtAbscissa(xPosition, m_evaluateContext);
|
|
m_graphWindow->setYAuto(false);
|
|
m_graphWindow->setYMin(yPosition - yRange/2.0f);
|
|
m_graphWindow->setYMax(yPosition + yRange/2.0f);
|
|
m_yCursorPosition = floatToPixel(Axis::Vertical, yPosition);
|
|
reload();
|
|
}
|
|
|
|
void GraphView::setVisibleCursor(bool visibleCursor) {
|
|
m_visibleCursor = visibleCursor;
|
|
layoutSubviews();
|
|
}
|
|
|
|
void GraphView::initCursorPosition() {
|
|
float center = (min(Axis::Horizontal)+max(Axis::Horizontal))/2.0f;
|
|
m_indexFunctionSelectedByCursor = 0;
|
|
Function * firstFunction = m_functionStore->activeFunctionAtIndex(0);
|
|
float fCenter = firstFunction->evaluateAtAbscissa(center, m_evaluateContext);
|
|
m_xCursorPosition = (bounds().width()-1.0f)/2.0f;
|
|
m_yCursorPosition = floatToPixel(Axis::Vertical, fCenter);
|
|
}
|
|
|
|
void GraphView::moveCursorHorizontally(KDCoordinate xOffset) {
|
|
bool outsideWindow = m_xCursorPosition < 0 || m_xCursorPosition > bounds().width() || m_yCursorPosition < 0 || m_yCursorPosition > bounds().height();
|
|
m_xCursorPosition = m_xCursorPosition + xOffset;
|
|
if (!outsideWindow && m_xCursorPosition < k_cursorMarginToBorder) {
|
|
float xRange = max(Axis::Horizontal) - min(Axis::Horizontal);
|
|
m_graphWindow->setXMin(pixelToFloat(Axis::Horizontal, floorf(m_xCursorPosition)-k_cursorMarginToBorder));
|
|
m_graphWindow->setXMax(min(Axis::Horizontal) + xRange);
|
|
m_xCursorPosition = m_xCursorPosition - floorf(m_xCursorPosition) + k_cursorMarginToBorder;
|
|
}
|
|
if (!outsideWindow && m_xCursorPosition > bounds().width() - k_cursorMarginToBorder) {
|
|
float xRange = max(Axis::Horizontal) - min(Axis::Horizontal);
|
|
m_graphWindow->setXMax(pixelToFloat(Axis::Horizontal, ceilf(m_xCursorPosition)+k_cursorMarginToBorder));
|
|
m_graphWindow->setXMin(max(Axis::Horizontal) - xRange);
|
|
m_xCursorPosition = bounds().width() - k_cursorMarginToBorder - ceilf(m_xCursorPosition) + m_xCursorPosition;
|
|
}
|
|
Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor);
|
|
float ordinate = f->evaluateAtAbscissa(pixelToFloat(Axis::Horizontal, m_xCursorPosition), m_evaluateContext);
|
|
m_yCursorPosition = floatToPixel(Axis::Vertical, ordinate);
|
|
if (!outsideWindow && m_yCursorPosition < k_cursorMarginToBorder) {
|
|
float yRange = max(Axis::Vertical) - min(Axis::Vertical);
|
|
m_graphWindow->setYMax(pixelToFloat(Axis::Vertical, floorf(m_yCursorPosition)-k_cursorMarginToBorder));
|
|
m_graphWindow->setYMin(max(Axis::Vertical) - yRange);
|
|
m_yCursorPosition = m_yCursorPosition - floorf(m_yCursorPosition) + k_cursorMarginToBorder;
|
|
}
|
|
if (!outsideWindow && m_yCursorPosition > bounds().height() - k_cursorMarginToBorder) {
|
|
float yRange = max(Axis::Vertical) - min(Axis::Vertical);
|
|
m_graphWindow->setYMin(pixelToFloat(Axis::Vertical, ceilf(m_yCursorPosition)+k_cursorMarginToBorder));
|
|
m_graphWindow->setYMax(min(Axis::Vertical) + yRange);
|
|
m_yCursorPosition = bounds().height() - k_cursorMarginToBorder - ceilf(m_yCursorPosition) + m_yCursorPosition;
|
|
}
|
|
reload();
|
|
}
|
|
|
|
Function * GraphView::moveCursorUp() {
|
|
float x = pixelToFloat(Axis::Horizontal, m_xCursorPosition);
|
|
Function * actualFunction = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor);
|
|
float y = actualFunction->evaluateAtAbscissa(x, m_evaluateContext);
|
|
Function * nextFunction = actualFunction;
|
|
float nextY = FLT_MAX;
|
|
for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) {
|
|
Function * f = m_functionStore->activeFunctionAtIndex(i);
|
|
float newY = f->evaluateAtAbscissa(x, m_evaluateContext);
|
|
if (newY > y && newY < nextY) {
|
|
m_indexFunctionSelectedByCursor = i;
|
|
nextY = newY;
|
|
nextFunction = f;
|
|
}
|
|
}
|
|
if (nextFunction == actualFunction) {
|
|
return nullptr;
|
|
}
|
|
m_yCursorPosition = floatToPixel(Axis::Vertical, nextY);
|
|
layoutSubviews();
|
|
return nextFunction;
|
|
}
|
|
|
|
Function * GraphView::moveCursorDown() {
|
|
float x = pixelToFloat(Axis::Horizontal, m_xCursorPosition);
|
|
Function * actualFunction = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor);
|
|
float y = actualFunction->evaluateAtAbscissa(x, m_evaluateContext);
|
|
Function * nextFunction = actualFunction;
|
|
float nextY = -FLT_MAX;
|
|
for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) {
|
|
Function * f = m_functionStore->activeFunctionAtIndex(i);
|
|
float newY = f->evaluateAtAbscissa(x, m_evaluateContext);
|
|
if (newY < y && newY > nextY) {
|
|
m_indexFunctionSelectedByCursor = i;
|
|
nextY = newY;
|
|
nextFunction = f;
|
|
}
|
|
}
|
|
if (nextFunction == actualFunction) {
|
|
return nullptr;
|
|
}
|
|
m_yCursorPosition = floatToPixel(Axis::Vertical, nextY);
|
|
layoutSubviews();
|
|
return nextFunction;
|
|
}
|
|
|
|
void GraphView::layoutSubviews() {
|
|
KDRect cursorFrame((int)m_xCursorPosition - (k_cursorSize+1)/2+1, (int)m_yCursorPosition - (k_cursorSize+1)/2+1, k_cursorSize, k_cursorSize);
|
|
if (!m_visibleCursor) {
|
|
cursorFrame = KDRectZero;
|
|
}
|
|
m_cursorView.setPosition(m_xCursorPosition, m_yCursorPosition);
|
|
m_cursorView.setFrame(cursorFrame);
|
|
}
|
|
|
|
void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
|
|
ctx->fillRect(rect, KDColorWhite);
|
|
drawGrid(ctx, rect);
|
|
drawAxes(Axis::Horizontal, ctx, rect);
|
|
drawAxes(Axis::Vertical, ctx, rect);
|
|
drawLabels(Axis::Horizontal, ctx, rect);
|
|
drawLabels(Axis::Vertical, ctx, rect);
|
|
for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) {
|
|
Function * f = m_functionStore->activeFunctionAtIndex(i);
|
|
drawExpression(f->expression(), f->color(), ctx, rect);
|
|
}
|
|
}
|
|
|
|
void GraphView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const {
|
|
float start = step*((int)(min(axis)/step));
|
|
Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal;
|
|
for (float x =start; x < max(axis); x += step) {
|
|
drawLine(ctx, rect, otherAxis, x, color);
|
|
}
|
|
}
|
|
|
|
void GraphView::drawGrid(KDContext * ctx, KDRect rect) const {
|
|
drawGridLines(ctx, rect, Axis::Horizontal, m_graphWindow->xScale(), k_gridColor);
|
|
drawGridLines(ctx, rect, Axis::Vertical, m_graphWindow->yScale(), k_gridColor);
|
|
}
|
|
|
|
float GraphView::min(Axis axis) const {
|
|
assert(axis == Axis::Horizontal || axis == Axis::Vertical);
|
|
return (axis == Axis::Horizontal ? m_graphWindow->xMin() : m_graphWindow->yMin());
|
|
}
|
|
|
|
float GraphView::max(Axis axis) const {
|
|
assert(axis == Axis::Horizontal || axis == Axis::Vertical);
|
|
return (axis == Axis::Horizontal ? m_graphWindow->xMax() : m_graphWindow->yMax());
|
|
}
|
|
|
|
float GraphView::evaluateExpressionAtAbscissa(Expression * expression, float abscissa) const {
|
|
m_evaluateContext->setOverridenValueForSymbolX(abscissa);
|
|
return expression->approximate(*m_evaluateContext);
|
|
}
|
|
|
|
}
|