[apps/shared][python/port] CurveView::drawArrow uses pixel computation

instead of float computation to avoid precision errors, by default the
arrow size is decided in pixels.
This commit is contained in:
Émilie Feral
2020-06-03 12:17:08 +02:00
parent a9fbcf99b2
commit 420dd04766
5 changed files with 63 additions and 22 deletions

View File

@@ -133,6 +133,22 @@ float CurveView::floatToPixel(Axis axis, float f) const {
}
}
float CurveView::floatLengthToPixelLength(Axis axis, float f) const {
float dist = floatToPixel(axis, f) - floatToPixel(axis, 0.0f);
return axis == Axis::Vertical ? - dist : dist;
}
float CurveView::floatLengthToPixelLength(float dx, float dy) const {
float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx);
float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy);
return std::sqrt(dxPixel*dxPixel+dyPixel*dyPixel);
}
float CurveView::pixelLengthToFloatLength(Axis axis, float f) const {
f = axis == Axis::Vertical ? -f : f;
return pixelToFloat(axis, floatToPixel(axis, 0.0f) + f);
}
void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor boldColor, KDColor lightColor) const {
Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal;
/* We translate the pixel coordinates into floats, adding/subtracting 1 to
@@ -449,7 +465,27 @@ void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor
}
void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWidth, float tanAngle) const {
void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith, float tanAngle) const {
/* TODO: all computations are done in pixels because doing them in float
* values led to approximation error (?). But this leads to useless back and
* forth between float and pixels. Find a proper way to handle approximation
* errors (if this was the problem). */
assert(tanAngle >= 0.0f);
if (std::fabs(dx) < FLT_EPSILON && std::fabs(dy) < FLT_EPSILON) {
// We can't draw an arrow without any orientation
return;
}
// Turn arrowWith in pixel length
float pixelArrowWith = 8.0f; // default value in pixels
if (arrowWith > 0.0f) {
float dxdy = std::sqrt(dx*dx+dy*dy);
float dxArrow = arrowWith*dx/dxdy;
float dyArrow = arrowWith*dy/dxdy;
pixelArrowWith = floatLengthToPixelLength(dxArrow, dyArrow);
}
/* Let's call the following variables L and l:
*
* / |
@@ -466,22 +502,22 @@ void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float
*
* ----- L -----
*
**/
assert(tanAngle >= 0.0f);
if (std::fabs(dx) < FLT_EPSILON && std::fabs(dy) < FLT_EPSILON) {
// We can't draw an arrow without any orientation
return;
}
float l = arrowWidth/2.0f;
float L = l/tanAngle;
float dx2dy2 = std::sqrt(dx*dx+dy*dy);
*/
float arrow1dx = L*dx/dx2dy2 + l*dy/dx2dy2;
float arrow1dy = L*dy/dx2dy2 - l*dx/dx2dy2;
float l = pixelArrowWith/2.0;
float L = l/tanAngle;
// We compute the arrow segments in pixels
float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx);
float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy);
float dx2dy2Pixel = floatLengthToPixelLength(dx, dy);
float arrow1dx = pixelLengthToFloatLength(Axis::Horizontal, L*dxPixel/dx2dy2Pixel + l*dyPixel/dx2dy2Pixel);
float arrow1dy = pixelLengthToFloatLength(Axis::Vertical, L*dyPixel/dx2dy2Pixel - l*dxPixel/dx2dy2Pixel);
drawSegment(ctx, rect, x, y, x - arrow1dx, y - arrow1dy, color, false);
float arrow2dx = L*dx/dx2dy2 - l*dy/dx2dy2;
float arrow2dy = L*dy/dx2dy2 + l*dx/dx2dy2;
float arrow2dx = pixelLengthToFloatLength(Axis::Horizontal, L*dxPixel/dx2dy2Pixel - l*dyPixel/dx2dy2Pixel);
float arrow2dy = pixelLengthToFloatLength(Axis::Vertical, L*dyPixel/dx2dy2Pixel + l*dxPixel/dx2dy2Pixel);
drawSegment(ctx, rect, x, y, x - arrow2dx, y - arrow2dy, color, false);
}

View File

@@ -57,6 +57,9 @@ protected:
constexpr static int k_externRectMargin = 2;
float pixelToFloat(Axis axis, KDCoordinate p) const;
float floatToPixel(Axis axis, float f) const;
float floatLengthToPixelLength(Axis axis, float f) const;
float pixelLengthToFloatLength(Axis axis, float f) const;
float floatLengthToPixelLength(float dx, float dy) const;
void drawLine(KDContext * ctx, KDRect rect, Axis axis,
float coordinate, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const {
return drawHorizontalOrVerticalSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color,
@@ -77,9 +80,9 @@ protected:
void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const;
/* 'drawArrow' draws the edge of an arrow pointing to (x,y) with the
* orientation (dx,dy).
* The parameters defining the shape of the arrow are the length of the base
* of the arrow triangle - 'arrowWith' - and the tangent of the angle between
* the segment and each wing of the arrow called 'tanAngle'.
* The parameters defining the shape of the arrow are the length of
* the base of the arrow triangle - 'pixelArrowWith' - and the tangent of the
* angle between the segment and each wing of the arrow called 'tanAngle'.
*
* / |
* / |
@@ -95,11 +98,11 @@ protected:
*
* <--- L --->
*
* l = arrowWith
* l = pixelArrowWith
* tanAngle = tan(angle) = l/2L
*/
void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith = 4, float tanAngle = 1.0f/3.0f) const;
void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith, float tanAngle = 1.0f/3.0f) const;
void drawGrid(KDContext * ctx, KDRect rect) const;
void drawAxes(KDContext * ctx, KDRect rect) const;
void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const;

View File

@@ -106,7 +106,9 @@ mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args)
mp_map_elem_t * elem;
// Setting arrow width
elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_head_width), MP_MAP_LOOKUP);
mp_obj_t arrowWidth = (elem == nullptr) ? mp_obj_new_float(0.003) : elem->value;
/* Default head_width is 0.0f because we want a default width in pixel
* coordinates which is handled by CurveView::drawArrow. */
mp_obj_t arrowWidth = (elem == nullptr) ? mp_obj_new_float(0.0f) : elem->value;
// Setting arrow color
KDColor color;

View File

@@ -78,7 +78,7 @@ public:
KDColor m_color;
};
void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth = mp_obj_new_float(0.0));
void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth = mp_obj_new_float(NAN));
Iterable<ListIterator<Segment>> segments() { return Iterable<ListIterator<Segment>>(m_segments); }
// Rect

View File

@@ -46,7 +46,7 @@ void PlotView::traceSegment(KDContext * ctx, KDRect r, PlotStore::Segment segmen
segment.xEnd(), segment.yEnd(),
segment.color()
);
if (segment.arrowWidth() > 0.0f) {
if (!std::isnan(segment.arrowWidth())) {
float dx = segment.xEnd() - segment.xStart();
float dy = segment.yEnd() - segment.yStart();
drawArrow(ctx, r, segment.xEnd(), segment.yEnd(), dx, dy, segment.color(), segment.arrowWidth());