TAChart: various problems with TIntervalList.Epsilon, and possible solutions
Original Reporter info from Mantis: Marcin Wiazowski
-
Reporter name:
Original Reporter info from Mantis: Marcin Wiazowski
- Reporter name:
Description:
TIntervalList is an internal helper object, currently used by four series:
- TExpressionSeries,
- TFuncSeries,
- TCubicSplineSeries,
- TFitSeries.
All these series have a Step property. When drawing the series, for every pixel on the horizontal axis (when Step = 1) / every second pixel on the horizontal axis (when Step = 2) / etc. / etc., function's Y value is calculated, and a straight line is drawn to the last calculated (X, Y) point.
The internal TIntervalList object implements this behavior. In Addition, some ranges can be omitted when drawing: for example, when we have Y := cotan(X), we may wish to exclude X = 0 from drawing, along with some range around - for example X values between -0.2 and 0.2. Excluded points and ranges give us an exclusion list.
The interesting TIntervalList's methods / properties are:
- procedure AddPoint(APoint: Double)
- procedure AddRange(AStart, AEnd: Double)
- function Intersect(var ALeft, ARight: Double): Boolean
- property Epsilon: Double
AddPoint() adds a point to the exclusion list - for example AddPoint(0).
AddRange() adds a range to the exclusion list - for example AddRange(-0.2, 0.2).
Intersect() is used internally, to check if, between X = ALeft and X = ARight, there is some excluded point or range.
Epsilon is used internally to enlarge excluded points / ranges - in both sides, by the Epsilon value. Default Epsilon value is 1E-6.
The attached Algorithm.png shows the idea of TIntervalList's work. Let's assume, that we want to draw some function in the X = 1 .. 5 range, and our chart's width is 101 pixels. For AStep = 1, we need to calculate function values for the following X values (in case when there are no excluded ranges in the middle):
X = 1 (pixel 1)
X = 1.04 (pixel 2)
X = 1.08 (pixel 3)
X = 1.12 (pixel 4)
...
X = 4.88 (pixel 98)
X = 4.92 (pixel 99)
X = 4.96 (pixel 100)
X = 5 (pixel 101)
Now let's assume that, for some reason, we requested excluding the X = 1.15 .. 1.25 range (red area on the image), and we have Epsilon set to 0.02 - in this case, the final excluded area will be 1.13 .. 1.27 (pink area on the image). The drawing algorithm will be:
- initialize X to 1 and Delta to 0.04
- call Intersect(X, X+Delta) to see if the X .. X+Delta = 1 .. 1.04 range intersects some excluded region -> no ->
draw a line from (X, func(X)) to (X+Delta, func(X+Delta)) -> set X to X+Delta, i.e. to 1.04
- call Intersect(X, X+Delta) to see if the X .. X+Delta = 1.04 .. 1.08 range intersects some excluded region -> no ->
draw a line from (X, func(X)) to (X+Delta, func(X+Delta)) -> set X to X+Delta, i.e. to 1.08
- call Intersect(X, X+Delta) to see if the X .. X+Delta = 1.08 .. 1.12 range intersects some excluded region -> no ->
draw a line from (X, func(X)) to (X+Delta, func(X+Delta)) -> set X to X+Delta, i.e. to 1.12
- call Intersect(X, X+Delta) to see if the X .. X+Delta = 1.12 .. 1.16 range intersects some excluded region -> YES, the 1.15 .. 1.25 region is intersected ->
draw a line from (X, func(X)) to (RangeLo-Epsilon=1.13, func(RangeLo-Epsilon=1.13)) -> set X to RangeHi+Epsilon, i.e. to 1.27 ->
move to (X, func(X)) to start a new line there
- call Intersect(X, X+Delta) to see if the X .. X+Delta = 1.27 .. 1.31 range intersects some excluded region -> no ->
draw a line from (X, func(X)) to (X+Delta, func(X+Delta)) -> set X to X+Delta, i.e. to 1.31
- ...
Now let's take a look at problems with the current TIntervalList implementation:
PROBLEM 1:
a) Launch Reproduce1 application and press the button 4 times. Application hangs forever.
b) Load Reproduce1 application in IDE, select ListChartSource, launch DataPoints editor, change first X value from -1000 to -1E15 and close the DataPoints editor by pressing Ok. Lazarus IDE hangs forever.
PROBLEM 2:
Launch Reproduce2 application. Series line is drawn NOT from the first point - there is a gap between the first point and the line's beginning.
PROBLEM 3:
Launch Reproduce3 application and press the button 3 times. Series' line disappears. Press the button 4 more times - series is drawn in a completely improper way.
PROBLEM 4:
Launch Reproduce4 application. Series line is drawn from X ~= -2.5 to X ~= 0, and then back to X = -0.3.
PROBLEM 5:
Launch Reproduce5 application and press the button - an exception "Epsilon <= 0" is raised, although documentation (at http://wiki.freepascal.org/TAChart_Tutorial:_Function_Series) says, that Epsilon can be set to 0: "you can show this point in the chart by setting Epsilon = 0".
PROBLEM 6:
Load Reproduce6 application in IDE, select Chart1ExpressionSeries, go to the DomainEpsilon property and try to change it to any value different than 1E-6. It always immediately reverts back to 1E-6. This isn't in fact a problem in the TIntervalList itself, but it's related.
For explanations, see below.
Mantis conversion info:
- Mantis ID: 35250
- Build: 60720
- Version: 2.1 (SVN)
- Fixed in revision: 60740 (#b2a4a786)
- Target version: 2.2