Skip to content

Releases: pisarev/pascal-crossgraph

Installation from nothing, and the palette section moves under it

Choose a tag to compare

@pisarev pisarev released this 18 Aug 00:27

The README now carries an Installation section: the component from an empty
folder - the whole test matrix by script, Delphi by hand, Lazarus by hand.
Putting the component on the palette moved under it, and building the engine
without a screen has a section of its own now. Every command was run from a
clean clone before this release went out, in an environment stripped of the
author's own settings.

Nothing in the code changed: the rest of the diff is long lines wrapped at
operator and argument boundaries, and all nine source files involved are
identical once whitespace is removed.

An extremum is found on the function, not on the sampling

Choose a tag to compare

@pisarev pisarev released this 16 Aug 15:02

An extremum marked on a curve is now a property of the function, not of the
sampling. Reported by a reader of the plugin, and reported precisely.

What was wrong

The engine walks a curve at a finite step and draws the result as a polyline. An
extremum was taken to be a vertex of that polyline - the nearest computed point,
not the extremum itself.

Three things followed, and all three were noticed:

  • for y = x*x the answer was not zero, and it did not agree with the value the
    report printed for that point;
  • the mark moved when the window or the quality changed, because the grid of
    computed points moved with them;
  • raising the accuracy tightened the grid and brought the answer closer without
    ever reaching it.

What it does now

The vertex of the polyline only opens the search. It gives a bracket of three
points, and inside that bracket the extremum is found on the function itself by
ternary search. What comes back is the point on the curve, not the sample that
happened to be nearest to it.

Ternary search rather than a parabola through the three points: a parabola is
tempting and wrong at a cusp, where the derivative does not exist. Ternary search
needs only that the bracket holds a single turn.

Refinement is skipped, and the vertex kept, where there is nothing to refine
against - in polar coordinates, where the parameter is an angle that the stored
point does not carry, and wherever the curve has no formula behind it.

Measured on a shifted parabola, a shifted sine and a cusp: the answer lands
within about a hundred-millionth of the analytic one, and it is the same answer
at two different zooms.

The check that used to pass while this was broken

A parabola tested at a symmetric window passed the whole time. Zero fell exactly
on a grid point, so the right answer came out by alignment rather than by
calculation, and the check could not fail for the reason it existed.

The checks that guard this now place the extremum deliberately off the grid, and
they ask for the same answer at two zooms rather than at one. The second question
is the one that matters: a mark that moves when you zoom is wrong even when it
looks close.

Compatibility

Delphi 10.2 Tokyo through 13, win32 and win64
Free Pascal 3.2.2 and 3.3.1, win32 and win64 and linux64

The plotting engine builds on Delphi 10.2 Tokyo through 13

Choose a tag to compare

@pisarev pisarev released this 15 Aug 21:59

The plotting engine builds on older Delphi now: 10.2 Tokyo through 13. Nothing
about how it draws has changed - this release is about which compilers will
accept the sources.

One argument changed type in the RTL

GdiplusStartup hands back a token. In 10.2 and 10.3 the RTL declares that
argument ULONG; from 10.4 it is ULONG_PTR. It is taken by reference, so the
types have to agree exactly - a near miss is not a warning but E2033.

The variable is now declared by a numeric test on the compiler version. The
engine's own storage for the token stays pointer-sized, so nothing else about
the code moved.

Why a number rather than the name of a generation

A conditional symbol with a dot in it does not ask what it appears to ask.
Delphi cuts the name at the dot, so DELPHI_10.4 is really a question about
DELPHI_10 - a symbol that has existed since 10 Seattle and therefore answers
yes on 10.2 as well. Measured on a 10.2 installation: all three of
DELPHI_10.2, DELPHI_10.3 and DELPHI_10.4 answer yes.

CompilerVersion is a number, and a number cannot be misread that way.

The table below is measured, not assumed

Each Delphi version in it is a real installation, and before a release every one
of them compiles all eight units of the engine one at a time. A version that is
not in the table is not claimed.

Compatibility

Delphi 10.2 Tokyo through 13, win32 and win64
Free Pascal 3.2.2 and 3.3.1, win32 and win64 and linux64

The component survives being dropped on a form

Choose a tag to compare

@pisarev pisarev released this 15 Aug 16:47

The component can be dropped on a form again. Under Free Pascal it used to
crash there, and when it survived it came up black.

The timer contract is not the same on both compilers

The component drives itself from a precise timer. Under Delphi that timer is a
window timer: the handler runs in the main thread, and painting from it is
ordinary painting. Under Free Pascal the same class runs a thread of its own and
calls the handler there. So the handler painted from a worker thread while the
main thread painted the same control, and the pen cache of the LCL does not
survive that. Measured before the fix: six crashes in ten runs. After: none.

The handlers no longer do the work. Each one posts a message to its own window,
and the work happens in the main thread where it belongs. The contract itself is
now written down in the header of the timer unit, so the next reader does not
have to discover it from a crash.

The same change closed a smaller defect: a wheel step could be lost. The zoom
direction was written by the main thread and read - and cleared - by the timer
thread. Both ends are in the main thread now.

Why it was black

Under the LCL the default Color of a control is clDefault. That is not a
colour but an instruction to ask the environment for one, and an off-screen
bitmap has no environment to ask: the request resolved to black. Under Delphi
the same line produced the ordinary window colour.

The buffer is now filled with a resolved colour, and the two fonts the component
owns are given explicit defaults instead of inheriting the same uncertainty.

Installing the package into the IDE

The Lazarus package now declares itself usable at design time and declares the
dependency it always had. Without both, the IDE either refused the package or
rebuilt itself without the units it needed, and the component never reached the
palette. Checked by installing it: the component can be placed on a form with
the mouse.

One thing we saw once and cannot explain yet

The new gate puts the component on a form over and over and checks that a graph
actually appeared. In one run of an early batch the picture came out empty. It
has not happened again in a hundred and thirty-eight runs since, and in none of
those runs did the thread watchdog fire or anything crash - the defect this
release fixes did not occur once.

We do not know the cause. It is written down here rather than left out, because
the check that found it did not exist before this release: nobody could have
seen this earlier. The gate stays in the matrix, and it now saves evidence at
the moment of an empty result - how much deferred work was posted and handled,
the state of the control, and the picture itself - so that the next occurrence
is a diagnosis rather than a guess.

Compatibility

Delphi 37.0 (Delphi 13), win32 and win64
Free Pascal 3.2.2 and 3.3.1, win32 and win64 and linux64

1.1.1 - Painting only when asked, and not erasing what it covers

Choose a tag to compare

@pisarev pisarev released this 14 Aug 11:12

The component stopped painting outside the repaint it is given, and it stopped
erasing a background it covers itself.

Three ways it painted out of turn

An invalidation called the painting routine directly. That left the window still
invalid, so the system sent a real repaint afterwards: the picture was drawn
twice, and the background was erased in between. If the second pass met a
different size or a different set of points, the two drawings did not line up.

Results from the worker threads - crossings and extrema - were drawn into the
buffer and put on screen the same way, out of turn. While a curve is being
computed those arrive in a stream, and each one competed with the ordinary
repaint of the window.

The tracing line was drawn over the buffer rather than into it, so it was gone
at the next repaint and flickered as the mouse moved.

All three now do the same thing: mark the window invalid and let the system
repaint once. The tracing line is drawn into the buffer with everything else.

The background is no longer erased under it

The control declares csOpaque. It fills every pixel it owns from its buffer,
so erasing the background first was a white flash and nothing more.

There is a second path for the case where the control has no window on screen -
a component living on a hidden form, with the picture taken from its buffer.
There the buffer has to be assembled by the direct call, and it is. Measured by
a gallery of reference images: without that split the buffer kept marks from the
previous pass and the antialiased edges darkened by 981 pixels on one of them.

Silent mode takes the control off the screen

Silent means the picture comes from an outer layer - a web page, in the plugin
that uses this component. A silent control has no use for screen space, and
while it had some, every resize of its container sent it a repaint it answered
with nothing.

Compatibility

Delphi 37.0 (Delphi 13), win32 and win64
Free Pascal 3.2.2 and 3.3.1, win32 and win64 and linux64

1.1.0 - The engine underneath changed, and so did the way this is built and checked

Choose a tag to compare

@pisarev pisarev released this 13 Aug 12:11

The plotting engine itself did not change in this release. What changed is
underneath it, and what changed is how it is built and checked.

The parser it draws with had a defect in results

The accelerator evaluated a / f(b) * c as a / (f(b) * c) whenever the right
operand of a division was a function call. Curves drawn from such a formula were
wrong, not merely slow. The fix is in pascal-mathparser; this release links
against it.

The value record also changed size on 32 bits - it is twenty-four bytes on every
target now. Anything that stored a compiled script is unaffected on 64 bits and
becomes portable between word sizes.

The package builds where it is asked to

crosspascal_graph.lpk writes its compiled units into graph_lib rather than a
shared lib, so installing the three packages side by side no longer mixes
their output. The build script for the tests now checks the version of the
compiler it was handed: the catalogue builds on 3.2.2, and a script that takes
whatever is in PATH was green here and red for the person who tried it.

Thirty-two bits

The component builds and its packages install on Windows i386 as well as win64
and linux64. The visual package legitimately needs the LCL, so a 32-bit LCL has
to exist for it - that is a property of the environment, not of this code, and
the acceptance run says so out loud when it is missing rather than passing
quietly.

Compatibility

Delphi 37.0 (Delphi 13), win32 and win64
Free Pascal 3.2.2 and 3.3.1, win32 and win64 and linux64

1.0.8 - The parser fixes, carried

Pre-release

Choose a tag to compare

@pisarev pisarev released this 07 Aug 21:17

A version bump that carries the parser fixes. The component itself is unchanged.

One of those fixes reaches the component directly. It computes curves on worker
threads, and a formula holding a call to Parse or Deriv starts a nested
parser. Until now that nested parser inherited the mask of whoever called it
rather than installing its own, so what it did on division by zero depended on
the caller. The pascal-mathparser release for 1.0.8 has the detail.

Delphi 13 and Free Pascal 3.3.1. The component draws through the VCL on Delphi
and the LCL on Lazarus; the engine itself needs neither and builds headless.

The engine says which compiler it needs

Choose a tag to compare

@pisarev pisarev released this 07 Aug 12:53

The engine has never built on Free Pascal 3.2.2 and cannot: CrossVision.Geometry
sorts points with an anonymous comparer, and function references arrived in
3.3.1. The parser next door does build with 3.2.2, which is where the confusion
came from.

The README now names the requirement. tests/build_linux.sh says so before it
starts, instead of stopping with a syntax error in the middle of a file you did
not write.

The engine also moves to the parser's new loop guard. ArmLoopGuard and
DisarmLoopGuard wrap a whole sweep in DoParse, and each single point in
ComputePolar and ComputeRectangular. A spent budget no longer outlives the
call that set it, and no longer refuses whatever runs next in the same thread.
The pascal-mathparser release for 1.0.7 has the detail.

Delphi 13 and Free Pascal 3.3.1. The component draws through the VCL on Delphi
and the LCL on Lazarus; the engine itself needs neither and builds headless.

v1.0.0

Choose a tag to compare

@pisarev pisarev released this 04 Aug 20:13

First public release.

A plotting engine and a visual component that draw what MathParser computes.
Formulas go in as text, curves come out - with the discontinuities, the
intersections and the extrema found for you.

  • Delphi and Lazarus, cartesian and polar coordinates
  • intersections and extrema are searched in worker threads
  • CenterTests pins the contract for the centre of the view: the panel shows a
    centre, the engine holds an offset, and the two differ in sign

The repository expects pascal-mathparser checked out beside it.

See it working: https://pisarev.github.io/mathparser-live/demo/