equations: Add graphical rendering for integrate

Render integrals graphically, in line with sums and products.

Fixes: #1174

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2024-09-12 00:32:37 +02:00
parent 3ac9ce15d6
commit 4d7cbf0b3a
9 changed files with 91 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
color-images/integral.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/integral-add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/integral-div.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/integral.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1725,7 +1725,7 @@ grob_p expression::prefix(grapher &g,
} }
grob_p expression::sumprod(grapher &g, bool isprod, grob_p expression::sumprod(grapher &g, id oid, sumprod_fn shape,
coord vi, grob_g index, coord vi, grob_g index,
coord vf, grob_g first, coord vf, grob_g first,
coord vl, grob_g last, coord vl, grob_g last,
@ -1738,20 +1738,39 @@ grob_p expression::sumprod(grapher &g, bool isprod,
using size = blitter::size; using size = blitter::size;
if (!index || !first || !last || !expr) if (!index || !first || !last || !expr || !shape)
return nullptr; return nullptr;
if (oid == ID_Integrate)
{
// Order of arguments is not identical between sum and integrate
// sum(index;first;last;expr)
// integrate(first;last;expr;index)
std::swap(last, expr); // integrate(first;last;index;expr)
std::swap(first, last); // integrate(first;index;last;expr)
std::swap(index, first); // integrate(index;first;last;expr)
}
auto fid = g.font; auto fid = g.font;
g.reduce_font(); g.reduce_font();
grob_g lower = infix(g, vi, index, 0, "=", vf, first); grob_g lower = (oid == ID_Integrate
? +first
: infix(g, vi, index, 0, "=", vf, first));
g.font = fid; g.font = fid;
if (!lower) if (!lower)
return nullptr; return nullptr;
if (oid == ID_Integrate)
{
expr = infix(g, ve, expr, 0, "d", vi, index);
ve = g.voffset;
if (!expr)
return nullptr;
}
grob::surface xs = expr->pixels(); grob::surface xs = expr->pixels();
size xh = xs.height(); size xh = xs.height();
size xw = xs.width(); size xw = xs.width();
grob_g sign = isprod ? product(g, xh) : sum(g, xh); grob_g sign = shape(g, xh);
if (!sign) if (!sign)
return nullptr; return nullptr;
@ -1829,6 +1848,29 @@ grob_p expression::product(grapher &g, blitter::size h)
} }
grob_p expression::integral(grapher &g, blitter::size h)
// ----------------------------------------------------------------------------
// Create an 'integral' sign of height h
// ----------------------------------------------------------------------------
{
using size = blitter::size;
size w = 32;
grob_g result = g.grob(w, h);
if (!result)
return nullptr;
grob::surface rs = result->pixels();
rs.fill (0, 0, w-1, h-1, g.background);
rs.fill (w/2-2, w/4, w/2+1, h-w/4, g.foreground);
rect rc = rs.clip();
rs.clip(0, 0, w, w/4);
rs.ellipse(w/2-1, 2, w-5, w/2-1, 3, g.foreground);
rs.clip(0, h-w/4, w, h-1);
rs.ellipse(4, h-w/2, w/2-0, h-3, 3, g.foreground);
rs.clip(rc);
return result;
}
static inline cstring mulsep() static inline cstring mulsep()
@ -1840,6 +1882,18 @@ static inline cstring mulsep()
} }
inline expression::sumprod_fn expression::sumprod_shape(id oid)
// ----------------------------------------------------------------------------
// Return the associated shape function
// ----------------------------------------------------------------------------
{
return oid == ID_Sum ? sum
: oid == ID_Product ? product
: oid == ID_Integrate ? integral
: nullptr;
}
grob_p expression::graph(grapher &g, uint depth, int &precedence) grob_p expression::graph(grapher &g, uint depth, int &precedence)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Render a single object as a graphical object // Render a single object as a graphical object
@ -2021,22 +2075,30 @@ grob_p expression::graph(grapher &g, uint depth, int &precedence)
case 4: case 4:
{ {
id oid = obj->type(); id oid = obj->type();
if (oid == ID_Sum || oid == ID_Product) if (sumprod_fn shape = sumprod_shape(oid))
{ {
int eprec = 0; int eprec = 0;
auto fid = g.font; auto fid = g.font;
grob_g expr = graph(g, depth, eprec); grob_g expr = graph(g, depth, eprec);
coord ve = g.voffset; coord ve = g.voffset;
g.reduce_font(); if (oid != ID_Integrate)
g.reduce_font();
grob_g last = graph(g, depth, eprec); grob_g last = graph(g, depth, eprec);
coord vl = g.voffset; coord vl = g.voffset;
if (oid == ID_Integrate)
{
// 'last' is really the expression
if (eprec < MULTIPLICATIVE)
last = parentheses(g, last);
g.reduce_font();
}
grob_g first = graph(g, depth, eprec); grob_g first = graph(g, depth, eprec);
coord vf = g.voffset; coord vf = g.voffset;
grob_g index = graph(g, depth, eprec); grob_g index = graph(g, depth, eprec);
coord vi = g.voffset; coord vi = g.voffset;
g.font = fid; g.font = fid;
return sumprod(g, oid == ID_Product, return sumprod(g, oid, shape,
vi, index, vi, index,
vf, first, vf, first,
vl, last, vl, last,

View file

@ -309,13 +309,17 @@ public:
coord vx, cstring pfx, coord vx, cstring pfx,
coord vy, grob_g y, coord vy, grob_g y,
int dir=0); int dir=0);
static grob_p sumprod(grapher &g, bool product,
coord vi, grob_g index, typedef grob_p (*sumprod_fn)(grapher &g, blitter::size h);
coord vf, grob_g first, static grob_p sumprod(grapher &g, id oid, sumprod_fn shape,
coord vl, grob_g last, coord vi, grob_g index,
coord ve, grob_g expr); coord vf, grob_g first,
static grob_p sum(grapher &g, blitter::size h); coord vl, grob_g last,
static grob_p product(grapher &g, blitter::size h); coord ve, grob_g expr);
static grob_p sum(grapher &g, blitter::size h);
static grob_p product(grapher &g, blitter::size h);
static grob_p integral(grapher &g, blitter::size h);
static sumprod_fn sumprod_shape(id oid);
public: public:

View file

@ -172,7 +172,7 @@ void tests::run(uint onlyCurrent)
{ {
here().begin("Current"); here().begin("Current");
if (onlyCurrent & 1) if (onlyCurrent & 1)
units_and_conversions(); editor_operations();
if (onlyCurrent & 2) if (onlyCurrent & 2)
demo_ui(); demo_ui();
if (onlyCurrent & 4) if (onlyCurrent & 4)
@ -1131,6 +1131,16 @@ void tests::editor_operations()
step("Implicit multiplication") step("Implicit multiplication")
.test(CLEAR, "'2X'", ENTER).expect("'2·X'"); .test(CLEAR, "'2X'", ENTER).expect("'2·X'");
step("Graphical rendering of integrals - Simple expression")
.test(CLEAR, "'integrate(A;B;sin(X);X)'", ENTER, EXIT)
.image_noheader("integral");
step("Graphical rendering of integrals - Additive expression")
.test(CLEAR, "'integrate(A;B;1+sin(X);X)'", ENTER, EXIT)
.image_noheader("integral-add");
step("Graphical rendering of integrals - Divide expression")
.test(CLEAR, "'integrate(A;B;1/sin(X);X)'", ENTER, EXIT)
.image_noheader("integral-div");
step("Enter X mod Y and checking it can be edited") step("Enter X mod Y and checking it can be edited")
.test(CLEAR, NOSHIFT, F, "X", RSHIFT, L, F3, "Y") .test(CLEAR, NOSHIFT, F, "X", RSHIFT, L, F3, "Y")
.editor("'X mod Y'") .editor("'X mod Y'")