Author image Kevin Ryde
and 1 contributors


App::Chart::Gtk2::Ex::LineClipper -- accumulate and/or draw connected line segments


 use App::Chart::Gtk2::Ex::LineClipper;

 # direct draw
     ($drawable, $gc, $line_width, $points_list);

 # OOP accumulator
 my $linedrawer = App::Chart::Gtk2::Ex::LineClipper->new (drawable => $drawable);
 $linedrawer->add ($gc, $x1, $y1);
 $linedrawer->add ($gc, $x2, $y2);


App::Chart::Gtk2::Ex::LineClipper helps you draw connected line segments like Gtk2::Drawable->draw_lines does, but with benefits of clipping wild coordinates (and not even sent to the server if not visible), and an accumulator mechanism to build line sequences.

Clipping wild X,Y values on the client side is important because Gdk silently takes just the low 16 bits of each. For example if you draw a line from 100,50 to 65736,50 you'll be unpleasantly surprised to find X=65736 comes out as X=200 (its low 16 bits), instead of extending to the far right hand end of the window.

The object-oriented accumulator is helpful if you've got a tricky loop generating points and colours for each segment and want someone else to keep track of what, if anything, you build up to draw.


App::Chart::Gtk2::Ex::LineClipper::clipped_draw_lines ($drawable, $gc, $line_width, $points)

Draw lines connecting the X,Y points in $points, like Gtk2::Drawable::draw_lines does, but clipping to the size of the drawable so huge coordinate values don't wrap around, and indeed are not sent to the server at all if completely off-screen.

$points is a reference to an array of X,Y values, one after the other. $drawable is a Gtk2::Gdk::Drawable (window, pixmap, etc) and $gc is a Gtk2::Gdk::GC. Eg.

        ($drawable, $gc, 1, [ 100,100, 200,200, 300,100 ]);

$line_width should be the width in pixels of the lines $gc will draw. This is used to know how far off the drawable the clipping must extend so the cap_style doesn't show. clipped_draw_lines doesn't simply read $line_width from $gc->get_values because that call does a round-trip to the X server. A width bigger than actually in use is fine; for instance you might just pass 20 pixels if you know you never draw lines wider than that. A width 0 is interpreted as a 1 pixel "thin line", the same as happens in the GC.

If $points is an empty array, or an array of just one X,Y point, then nothing at all is drawn. This handling of one point adds certainty to what plain $win->draw_lines does; for it a single X,Y of a 0-width "thin line" might or might not be drawn, depending on the server.


App::Chart::Gtk2::Ex::LineClipper->new (drawable => $drawable, ...)

Create and return a new LineClipper object to accumulate and draw connected line segments on $drawable using clipped_draw_lines above. $drawable is a Gtk2::Gdk::Drawable object as above, and a line_width parameter can be passed to set that for the clipping (again as above).

    my $linedrawer = App::Chart::Gtk2::Ex::LineClipper->new
                         (drawable => $drawable,
                          line_width => 5); # pixels

By default a single solitary X,Y point is not drawn, on the principle that it's not a line segment, but the draw_point option can be set true to have it shown as a circle of the given line_width. Eg.

    my $linedrawer = App::Chart::Gtk2::Ex::LineClipper->new
                         (drawable => $drawable,
                          line_width => 5,  # pixels
                          draw_point => 1); # true

If you're wondering that line_width used as a diameter won't come out circular if the pixels aren't square, well, yes, but the same is true of the line drawing. The single width used vertically and horizontally for the line segments makes them appear wider or narrower according to their angle.

$linedrawer->add ($gc, $x, $y)

Add a point to $linedrawer. The points accumulated will have connected line segments drawn from each to the next, in the order they're added.

$gc is a Gtk2::Gdk::GC for the new segment, ie. from the current endpoint to the new $x,$y. Different GCs can be used for different segments and the LineClipper takes care of passing runs of the same GC to clipped_draw_lines.


Draw the line segments accumulated, if any, and set $linedrawer back to empty, ready to start a new completely separate sequence of points.

You can use this to force a gap between points, ie. draw everything accumulated so far and make subsequent points a new run.

end is called automatically when $linedrawer is destroyed, which means that if you've got a complicated loop generating the points then you can just return or jump out from multiple places, confident that letting the LineClipper go out of scope will flush what you've accumulated.


The choice between the direct clipped_draw_lines and the accumulator object is purely a matter of convenience. If you've got all your values in an array and want to draw just one colour (one GC) then the best idea is to map or whatever to scale and build an X,Y array, then call clipped_draw_lines. But if you're going to make decisions about skipping some points or changing colours or leaving gaps while passing over the data then you may find the accumulator easier.

When switching to a different GC with add, the join between the lines in the old and new GCs doesn't use the join style (Gtk2::Gdk::JoinStyle) of either GC, but rather merely gets the cap style (Gtk2::Gdk::CapStyle) of each where the draw_lines from one ends and the other begins. This means you can't get a nice mitre or bevel when changing colours etc. For now it seems far too much trouble to try to do anything about that. The suggestion would be to use a cap style of round which comes out looking nice at any angle. Or if you only do GC switching with lines a few pixels wide then one or two slightly off where they meet will be hardly noticeable.


Gtk2::Gdk::Drawable, Gtk2::Gdk::GC, Gtk2::Ex::WidgetBits