racket / pict

Other
15 stars 21 forks source link

Polyline/path function #81

Open mgunyho opened 9 months ago

mgunyho commented 9 months ago

Hi! I'm learning Racket during Advent of Code, and I'm really enjoying how easy it is to create quick visualizations of the problems using Pict. However, I have several times needed to draw arbitrary polygons or paths from a list of xy coordinates, and I was surprised that there's no such function in Pict. I think a general-purpose drawing library should have a function like this (ideally, there should be something equivalent to SVG paths, with Bezier curves and such, but that's a bit more advanced).

Here's a quick function I wrote to do this:

(define (polyline coords #:closed? [closed? #t])
  ; coords: list of xy pairs in the form (cons x y)
  (define min-x (apply min (map car coords)))
  (define min-y (apply min (map cdr coords)))
  (define size-x (- (apply max (map car coords)) min-x))
  (define size-y (- (apply max (map cdr coords)) min-y))
  (define coords-shifted (for/list ([xy coords])
                           (cons (- (car xy) min-x) (- (cdr xy) min-y))))
  (dc (λ (dc dx dy)
        (define old-brush (send dc get-brush))
        (send dc set-brush (new brush% [style 'transparent]))

        (define path (new dc-path%))
        (send path move-to (car (first coords-shifted)) (cdr (first coords-shifted)))
        (for-each (λ (xy) (send path line-to (car xy) (cdr xy))) (rest coords-shifted))
        (cond [closed? (send path close)])

        (send dc draw-path path dx dy)
        (send dc set-brush old-brush)
        )
      size-x size-y))

It would of course need arguments to properly specify the line color and fill style etc. I'm also not sure if a list of pairs is the best way to specify the coordinates (pict seems to deal with x and y separately instead of using 2D vectors).

Do you think that something like this should be included in the library?

rfindler commented 9 months ago

There is a function like that in the 2htdp/image library and you can pass images from that library to the pict library. Here's an example:

#lang racket
(require (prefix-in 2: 2htdp/image)
         (prefix-in p: pict)
         lang/posn)

(p:hc-append (2:polygon (list (make-posn 0 100)
                              (make-posn 50 0)
                              (make-posn 0 0))
                        "solid"
                        "blue")
             (p:filled-rectangle 100 100))
mgunyho commented 9 months ago

I see, thanks. I saw 2htdp/image, but it was a bit unclear to me if I should use that or Pict. The documentation page you linked says it's a "teachpack", so I thought that it's more for beginners at programming (I'm aware that Racket/HtDP is used for education), and I went with pict.

If pict doesn't want to re-implement functionality from 2htdp, maybe the documentation could mention what kind of functionality can be found there instead of pict.

Also, to be honest, your suggestion is quite verbose, and it's annoying to prefix everything with 2: or p:. It would be weird to have such examples in the docs of pict.

After writing my initial comment I also found metapict which has the functionality I want (including Bezier curves), but that feels like a big additional library to learn, and as far as I can tell it's not maintained by the core Racket developers so it's not so "official".

soegaard commented 9 months ago

If you want to use coordinates to describe picts, then I suggest to take a look at metapict.

https://docs.racket-lang.org/metapict/index.html