Patrick (patrickwonders) wrote,
Patrick
patrickwonders

  • Mood:

Delightful Geometric Discovery

So, I wanted to make (regular-)hexagonal or square pie charts. Easy enough, right? Except, I wanted the area to represent the proportion, not the angle.

With a circle, those notions are equivalent. If you sweep out x% of 2π radians, you've covered x% of the area of the circle. Of course, you've also covered x% of the circumference.

Circle area and arc length

With regular polygons of fewer than infinitely many sides, the situation is not so clear. Sweeping out a small angle that doesn't include a corner covers less area than that same angle into a corner.

But, when I started trying to figure out the relationship, I was stunned. [Now, maybe y'all knew this before, but it was quite a pleasant surprise to me.] How are we going to figure it out? If it crosses a corner, we're going to break it apart into pieces along the radii from the center to the corners. Then, we're left with triangles.

The area of a triangle is half the base times the height. Well, for a regular polygon, the height is the in-radius (the radius of the largest circle that will fit inside the polygon) no matter which side of the polygon we're using for an edge.

Pentagon area and arc length

So, if we have a pentagon and sweep out some angle through one corner, we have one edge of length a and one edge of length b. So, the area of that wedge is proportional to a+b.

The area we sweep out is proportional to the amount of perimeter we trace over! So, if we know the total perimeter and we want to sweep out x% of the area, we need to sweep out x% of the perimeter. There's still some funky modular arithmetic to translate x% of the perimeter back into an angle, but it's very tractable.

And, once you've got the algorithm down, you can easily tackle pie charts of regular heptadecagons.

Heptadecagon area and arc length

In fact, all that's really required is that you can draw a circle that is tangent to every edge. That ensures that every triangle has the same height.

In retrospect, I should have realized all of this when deriving the equations for integrating in polar coordinates. But, there, I thought that there was something special going on since every radius is perpendicular to the circle. Alas, this all delights me today.

I whipped out OmniGraffle Professional to throw together these diagrams. The version that I have doesn't have any way that I can see to make arbitrary, regular polygons. There's a new version out, but I don't use the program enough at the moment to justify the upgrade license cost. And, I don't even know if the new version does regular polygons.

So, I again pulled out xach's Vecto library for Lisp. It took me a couple false starts to understand the clipping paths. But, the end result is very nice.

(require :asdf)
(asdf:operate 'asdf:load-op 'vecto)

(use-package 'vecto)

(defun to-radians (degrees) (* degrees (/ pi 180.0)))

(defun get-regular-poly-points (nn radius)
    (let ((points nil))
        (dotimes (ii nn points)
            (let* ((angle (* 2 pi (+ ii 1/2) (/ nn)))
                   (xx    (* (cos angle) radius))
                   (yy    (* (sin angle) radius)))
                (push (cons xx yy) points)))))

(defun draw-poly (points)
    (move-to (caar points) (cdar points))
    (dolist (pp (cdr points))
        (line-to (car pp) (cdr pp)))
    (close-subpath))

(defun draw-wedge-picture (ww hh filename wedge-scale clip-shape label)
    (with-canvas (:width ww :height hh)
        (let ((font (get-font "font.ttf"))
              (radius (* 4/5 hh))
              (angle1 (to-radians 14))
              (angle2 (to-radians 65)))

            (set-rgba-fill 1 1 1 0.75)
            (rectangle 0 0 ww hh)
            (fill-path)

            (with-graphics-state
                (translate (/ ww 4) (/ hh 8))
                (set-rgb-stroke 0 0 0)
                (set-line-width (/ hh 64))
                (set-line-join :miter)

                ;; draw polygon
                (set-rgb-fill 0.3 0.3 0.7)
                (funcall clip-shape radius)
                (fill-path)

                (with-graphics-state
                    (funcall clip-shape radius)
                    (clip-path)
                    (end-path-no-op)

                    ;; draw pie wedge
                    (set-rgb-fill 0.7 0.5 0.3)
                    (move-to 0 0)
                    (arc 0 0 (* wedge-scale radius) angle1 angle2)
                    (close-subpath)
                    (fill-and-stroke))

                (funcall clip-shape radius)
                (stroke)

                ;; draw illustration of arc length
                (with-graphics-state
                    (move-to 0 0)
                    (arc 0 0 (* 2 radius) angle1 angle2)
                    (clip-path)
                    (end-path-no-op)

                    (funcall clip-shape (* 10/9 radius))
                    (stroke))

                ;; prep the font
                (set-rgb-fill 0 0 0)
                (set-font font (* radius 1/5))
                ;; label the wedge
                (let* ((mid-angle  (/ (+ angle1 angle2) 2))
                       (txt-radius (* radius 2/5))
                       (txt-x      (* (cos mid-angle) txt-radius))
                       (txt-y      (* (sin mid-angle) txt-radius)))
                    (draw-centered-string txt-x
                                          (- txt-y (* radius 1/16))
                                          #( #x398 )))

                ;; label the arc length
                (draw-centered-string (* 13/10 radius) (* 3/5 radius) label))

            ;; draw a nice border around the whole image
            (set-line-width (/ hh 64))
            (set-rgb-stroke 0 0 0)
            (rectangle 0 0 ww hh)
            (stroke)

            (save-png filename))))

(defun circle-arc-length (ww &optional (hh (floor (* ww 9/16))))
    (draw-wedge-picture ww hh "circle-arc-length.png" 1
            #'(lambda (radius) (centered-circle-path 0 0 radius))
            #( #\r #x398 )))

(defun poly-arc-length (nn filename label ww &optional (hh (floor (* ww 9/16))))
    (draw-wedge-picture ww hh filename 2
            #'(lambda (radius) (draw-poly (get-regular-poly-points nn radius)))
            label))

(defconstant +width+ 512)

(circle-arc-length +width+)
(poly-arc-length  5 "penta-arc-length.png" "a+b" +width+)
(poly-arc-length 17 "heptadeca-arc-length.png" "a+b+c" +width+)
Tags: geometry, lisp, math
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

  • 3 comments