Board index » delphi » PolyLine,TPoint

PolyLine,TPoint

This works:

procedure TfrmIVALPLOT.FormPaint(Sender: TObject);
var k:integer; Points:array of TPoint;
begin
      SetLength(Points,n+1);   // n is a global variable previously
determined. Points has to be a dynamic array,
                                           // since n is determined by
user input.
      Points[0].X:= "some starting point";
      Points[0].Y:= "some starting point";
      for k:= 1 to n do begin
           Points[k].X:= "determined by call to a function";
           Points[k].Y:= "determined by call to a function";
      end;
      with Canvas do PolyLine([Points[0],Points[n]]);
end;

This draws a straight line between points[0] and points[n].  But of
course I want a polyline connecting all the points in the array.  But

    "with Canvas do PolyLine(Points)"  produces nothing.

(Actually, it sometimes produces the correct plot, but usually nothing!)

Delphi Help implies you can pass any TPoint array as parameter of
PolyLine.  But in the example it gives each of the points in the array
is specified individually, as above.  I've got, potentially, millions of
points in the array.  How do I pass the whole thing, and get PolyLine to
do what it promises?

Thanks, Ben Crain
bcr...@gmu.edu

 

Re:PolyLine,TPoint


Quote
"Ben W. Crain" wrote:
> This works:

> procedure TfrmIVALPLOT.FormPaint(Sender: TObject);
> var k:integer; Points:array of TPoint;
> begin
>       SetLength(Points,n+1);   // n is a global variable previously
> determined. Points has to be a dynamic array,
>                                            // since n is determined by
> user input.
>       Points[0].X:= "some starting point";
>       Points[0].Y:= "some starting point";
>       for k:= 1 to n do begin
>            Points[k].X:= "determined by call to a function";
>            Points[k].Y:= "determined by call to a function";
>       end;
>       with Canvas do PolyLine([Points[0],Points[n]]);
> end;

> This draws a straight line between points[0] and points[n].  But of
> course I want a polyline connecting all the points in the array.  But

>     "with Canvas do PolyLine(Points)"  produces nothing.

> (Actually, it sometimes produces the correct plot, but usually nothing!)

> Delphi Help implies you can pass any TPoint array as parameter of
> PolyLine.  But in the example it gives each of the points in the array
> is specified individually, as above.  I've got, potentially, millions of
> points in the array.  How do I pass the whole thing, and get PolyLine to
> do what it promises?

    I don't have D4 so I don't know about those dynamic array things.
I'm surprised that PolyLine(Points) doesn't always work. Try again
a few times: a possibility is that it works for small arrays but
not for large ones, where you could determine the definitions
of "small" and "large" by experiment. (There's still a lot of 16-bit
stuff in the GDI is the problem).

    If it works for fairly small arrays then the answer is to use
small arrays, piecing them together to give large arrays. I
bet that works. (If you can't get "small" arrays working
consistently you can use the method that works in D3:
allocate memory for a pointer and use Slice. That will
only work for "small" arrays, and if they did the
dynamic arrays right it shouldn't be necessary anyway.)

Re:PolyLine,TPoint


Quote
"David C. Ullrich" wrote:
> I'm surprised that PolyLine(Points) doesn't always work. Try again
> a few times: a possibility is that it works for small arrays but
> not for large ones, where you could determine the definitions
> of "small" and "large" by experiment. (There's still a lot of 16-bit
> stuff in the GDI is the problem).

I think you got it there.  TCanvas.PolyLine is a wrapper around a windows
API.  Wouldn't surprise me one bit to find that there wasn't something like a
16K hardcoded in there somewhere.

--
Bob Lee
High Performance Delphi - http://www.econos.com/optimize/
Updated June 7

Re:PolyLine,TPoint


Quote
"David C. Ullrich" wrote:
> "Ben W. Crain" wrote:

> > This works:

> > procedure TfrmIVALPLOT.FormPaint(Sender: TObject);
> > var k:integer; Points:array of TPoint;
> > begin
> >       SetLength(Points,n+1);   // n is a global variable previously
> > determined. Points has to be a dynamic array,
> >                                            // since n is determined by
> > user input.
> >       Points[0].X:= "some starting point";
> >       Points[0].Y:= "some starting point";
> >       for k:= 1 to n do begin
> >            Points[k].X:= "determined by call to a function";
> >            Points[k].Y:= "determined by call to a function";
> >       end;
> >       with Canvas do PolyLine([Points[0],Points[n]]);
> > end;

> > This draws a straight line between points[0] and points[n].  But of
> > course I want a polyline connecting all the points in the array.  But

> >     "with Canvas do PolyLine(Points)"  produces nothing.

> > (Actually, it sometimes produces the correct plot, but usually nothing!)

> > Delphi Help implies you can pass any TPoint array as parameter of
> > PolyLine.  But in the example it gives each of the points in the array
> > is specified individually, as above.  I've got, potentially, millions of
> > points in the array.  How do I pass the whole thing, and get PolyLine to
> > do what it promises?

>     I don't have D4 so I don't know about those dynamic array things.
> I'm surprised that PolyLine(Points) doesn't always work. Try again
> a few times: a possibility is that it works for small arrays but
> not for large ones, where you could determine the definitions
> of "small" and "large" by experiment. (There's still a lot of 16-bit
> stuff in the GDI is the problem).

>     If it works for fairly small arrays then the answer is to use
> small arrays, piecing them together to give large arrays. I
> bet that works. (If you can't get "small" arrays working
> consistently you can use the method that works in D3:
> allocate memory for a pointer and use Slice. That will
> only work for "small" arrays, and if they did the
> dynamic arrays right it shouldn't be necessary anyway.)

Thanks for the suggestion.  It looks like you're right:  PolyLine with a
dynamic TPoint array works fine for sizes up to about 16,000, then produces
nothing.  I don't, of course, need an array that large for plotting, since we
have far fewer pixels to work with.  But I was being lazy, and just taking my
array of results, which can be several million points, and plotting them
directly onto the screen (after converting to the appropriate pixel
positions), using the LineTo method, point by point.  Delphi says PolyLine is
better, so I try it, and run into this size limitation.  Which will now force
me to do what I've been postponing, namely, cut down the array used for
plotting to an appropriately small size. (I have to keep the original output
array, no matter how large, since it contains the estimated solution to a
differential equation;  cutting it down would discard real info, and degrade
subsequent interpolations.)  But now I wonder:  will code that assigns only a
few points of the output array to the TPoint array take more time than just
plotting the whole output array directly with LineTo?  I'll experiment with
that.

Re:PolyLine,TPoint


Quote
"Ben W. Crain" wrote:
>  But now I wonder:  will code that assigns only a
> few points of the output array to the TPoint array take more time than just
> plotting the whole output array directly with LineTo?  I'll experiment with
> that.

The cutting down (I belive it is called dessication) will be much faster than
plotting.  Here is a version of this that I've been using it will result in no
loss of display information.

FPoints is an array of TPoints.

DessicateSame checks to see if a point added to FPoints is a duplicate of the
previous.

Desicate Line drops intermediate points that lie along the same line.  Thus
keeping only the endpoints.

GetPoints adds floating point xy data to FPoints.

function TSeries.DesicateSame:boolean;
begin
 Result:=false;
 if (FPoints[FPointCount-1].X=FPoints[FPointCount-2].X) then
  if (FPoints[FPointCount-1].Y=FPoints[FPointCount-2].Y) then
  begin
   dec(FPointCount);
   Result:=true;
  end;
end;

procedure TSeries.DesicateLine;
var
 X1,X2,Y1,Y2,X13,X12,Y13,Y12:integer;
begin
 if not DesicateSame then
 begin
  X1:=FPoints[FPointCount-3].X;
  Y1:=FPoints[FPointCount-3].Y;
  X2:=FPoints[FPointCount-2].X;
  Y2:=FPoints[FPointCount-2].Y;
  X13:=X1 - FPoints[FPointCount-1].X;
  X12:=X1 - X2;
  Y13:=Y1 - FPoints[FPointCount-1].Y;
  Y12:=Y1 - Y2;
  if (X13*Y12=X12*Y13) then
   if (X13*Y1-Y13*X1=X13*Y2-Y13-X2) then
   begin
    FPoints[FPointCount-2]:=FPoints[FPointCount-1];
    dec(FPointCount);
   end;
 end;
end;

procedure TSeries.GetPoints;
var
 i,num:integer;
begin
 if FXArray.Count<FYArray.Count then
  num:=FXArray.Count
 else
  num:=FYArray.Count;
 if num<FDataCount then
 begin
  Changed;
  FDataCount:=0;
 end;
 if ((FPointCount=0) and (num>0)) then
 begin
  with FPoints[0] do
  begin
   X:=round(FXArray.Value[0]*FXScale+FXOffset);
   Y:=round(-FYArray.Value[0]*FYScale+FYOffset);
  end;
  FPointCount:=1;
  FDataCount:=1;
 end;
 for i:=FDataCount to num-1 do
 begin
  with FPoints[FPointCount] do
  begin
   X:=round(FXArray.Value[i]*FXScale+FXOffset);
   Y:=round(-FYArray.Value[i]*FYScale+FYOffset);
  end;
  inc(FPointCount);
  if FPointCount>2 then
   DesicateLine
  else if FPointCount=2 then
   DesicateSame;
 end;
 FDataCount:=num;
end;

--
Bob Lee
High Performance Delphi - http://www.econos.com/optimize/
Updated June 7

Re:PolyLine,TPoint


Quote
"Ben W. Crain" wrote:
> [...] I was being lazy, and just taking my
> array of results, which can be several million points, and plotting them
> directly onto the screen (after converting to the appropriate pixel
> positions), using the LineTo method, point by point.  Delphi says PolyLine is
> better, so I try it, and run into this size limitation.  Which will now force
> me to do what I've been postponing, namely, cut down the array used for
> plotting to an appropriately small size. (I have to keep the original output
> array, no matter how large, since it contains the estimated solution to a
> differential equation;  cutting it down would discard real info, and degrade
> subsequent interpolations.)  But now I wonder:  will code that assigns only a
> few points of the output array to the TPoint array take more time than just
> plotting the whole output array directly with LineTo?  I'll experiment with
> that.

    I'm not certain I understand the question, (and I haven't tried looking at
Lee's reply to see if I can figure out the question from his answer, that
would be cheating.)

    In general doing things like assigning data to arrays is _much_ faster
than GDI functions - if you have 1000000 points then 1000 calls to
PolyLine with 1000-point subarrays should be much faster than
1000000 calls to LineTo.

    I _think_ that what you're worrying about is the time it takes to
copy the small number of points from the large array into the
small array that you're going to pass to Polyline. I really think
that that time will be irrelevant compared to everything else.
I also think that one advantage to doing it the D2/D3 way,
with pointers and Slice instead of actual "dynamic arrays",
is that the copying your worried about would not need to
be done at all! You say something like

type
    PPoints = ^TPoints;
    TPoints = array[0..1000000000] of TPoint;

var P, Q: PPoints;
begin
P:= @(Whatever array is holding all that data)
//or something similar

for j:=0 to 1000 do
begin
  Q:= @P^[j*1000];
  Canvas.Polyline(Slice(Q^), 1000));
end;

That's psuedo-code; I couldn't possibly give correct code without
knowing a little more, so there's no point in worrying about the
off-by-one errors or the LineTo's stitching the pieces together.
      But something like that will work, and the point is that
if my understanding of _how_ it works is correct there's no
large amounts of data being copied: the pointer is simply set
to the start of the next "small" subarray in-place, the Slice
quasi-function tells the compiler to lie about the size of the
array Q^ in exactly the way you want, and you're set.

    I _bet_ that doing the same thing with the D4 dynamic arrays
entails copying a lot of data. Not that I think that really matters,
compared to the PolyLine calls.

   If you want to plot pictures of solutions to differential equations
the thing to use is PolyBezier - you can get _very_ nice plots with
a surpisingly small number of nodes. (Usually the problem with
PolyBezier is you don't know the tangent vectors. But if it's a
solution to a DE then the DE tells you what the tangent vectors
are...)

Re:PolyLine,TPoint


Thanks to my respondents for their suggestions.  I don't really need such
extensive code for "dessicating", but it suggested how to do it simply.  I
have to cycle through the array of real number results and convert them into
pen positions for possible plotting.  If there is no change in the pen
position from one calculation to the next, ignore it, otherwise store it in
the TPoint array.  Since I insist on making the TPoint array dynamic, I
start out with its length 1, then increment that length by 1 prior to each
new assignment, ending up with an array of exactly the right size, not
requiring splicing.  This doesn't dessicate away straight lines, but
straight lines are very, very rare in my results; the extra work to get rid
of them doesn't seem worth it.  Plotting with PolyLine is then very fast.
Any extra speed would be lost on the user, since the plot itself is now
virtually instantaneous.  (In a sense that is a drawback.  Seeing it all at
once, in a flash, dessicates away all drama from watching the solution
unfold visually.)
Using Splice on a TPoint array doesn't work.  Error message says it must be
an "array" -- but of course it is, just a dynamic one.  That must confuse
Splice.
PolyBezier offers no advantages over PolyLine, and has the drawback of
requiring just the right number of points in the array, which appears to be
4 plus an integer multiple of 3.  Why bother fudging the array to fit that,
when the results are indistinguishable from PolyLine?  Of course PolyBezier
is different with very small arrays, giving a nice cubic rather than jagged
linear interpolation.  But my results have so many points that linear
PolyLine is visually indistinguishable from higher order curve fitting.
Quote

Re:PolyLine,TPoint


Quote
David C. Ullrich wrote:
> be done at all! You say something like

> type
>     PPoints = ^TPoints;
>     TPoints = array[0..1000000000] of TPoint;
> for j:=0 to 1000 do
> begin
>   Q:= @P^[j*1000];
>   Canvas.Polyline(Slice(Q^), 1000));

I wonder what screen sizes do you have?
Would not Canvas.FillRect do the same?
Klaus

Re:PolyLine,TPoint


Quote
"Klaus J. Koch" wrote:
> David C. Ullrich wrote:

> > be done at all! You say something like

> > type
> >     PPoints = ^TPoints;
> >     TPoints = array[0..1000000000] of TPoint;

> > for j:=0 to 1000 do
> > begin
> >   Q:= @P^[j*1000];
> >   Canvas.Polyline(Slice(Q^), 1000));

> I wonder what screen sizes do you have?
> Would not Canvas.FillRect do the same?
> Klaus

    Not sure whether this is a joke - congratulations! (If you're
serious,
well, whether FillRect does the same would depend on the array of
points... )

Other Threads