ClassPackage: cgToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 8.1
New since 8.1 release.

plot-widget

This functionality was added in an update released in the Fall of 2008. You must have that update to use this functionality. See sys:update-allegro for information on getting updates.

The class of the plot-widget.

The plot-widget is a facility for creating two-dimensional plots of X/Y coordinate pairs, such as scatter plots. (To instead create bar charts or line graphs for a sequence of items, see the chart-widget class.)

The plot-widget can be used to present static information or to dynamically monitor information as it changes. The widget will automatically lay out its axes and other parts (according to style properties that you can modify), and can also autocompute a rounded value range that encloses all of the data values.

Plot subobjects

The following classes are used to create secondary objects that are used by a plot-widget:

Each of those classes has its own properties that affect the style and behavior of the plot-widget itself.

Properties of the plot-widget class

These properties are unique to the plot-widget class.

Additional plot-widget properties are provided by the chart-or-plot superclass.

plot-widget examples

The following example code demonstrates the two main alternate techniques for supplying the data for a plot: (1) calling set-plot-value once for each datum, and (2) supplying a plot-value-returner function that will be called as needed to return each value.

For both techniques we will use the following sample data. This is an array of arbitrary X/Y coordinate pairs. The outermost dimension of the array represents two independent objects, each of which has its own set of X/Y pairs. Typically, different icons will be used in a plot to distinguish the data of different objects.

(defparameter *points*

  ;; Points for the first chart object.
  #3a(((51.68 18.49) (59.28 41.71) (10.26 130.29) (12.16 144.05)
       (20.14 136.31) (52.44 42.57) (49.4 59.77) (50.16 72.67)
       (41.42 76.97) (35.72 92.45) (33.06 112.23) (39.52 110.51)
       (47.88 95.89) (38.76 89.87) (25.84 131.15) (17.86 119.11)
       (30.4 104.49) (44.08 89.01) (55.86 70.09) (47.5 52.03)
       (44.08 70.95) (51.68 94.17) (36.86 105.35) (39.9 122.55)
       (23.94 120.83) (16.72 132.01) (31.92 124.27) (56.24 31.39)
       (53.58 55.47) (49.4 83.85) (43.32 105.35))

      ;; Points for the second chart object.
      ((37.0 123.0) (53.58 138.03) (45.98 140.61) (43.32 119.97)
       (25.08 75.25) (24.32 123.41) (37.24 89.01) (39.52 101.91)
       (34.96 107.93) (30.78 114.81) (24.32 112.23) (25.08 99.33)
       (25.08 89.87) (20.9 109.65) (18.24 87.29) (11.02 69.23)
       (12.54 58.05) (17.86 40.85) (19.38 58.05) (25.08 63.21)
       (21.66 81.27) (13.68 88.15) (29.64 128.57) (20.9 118.25)
       (16.72 113.09) (17.1 98.47) (22.42 101.91) (30.02 83.85)
       (33.82 101.05) (28.88 96.75) (27.36 119.11))))

The first version of the example code uses the approach of calling set-plot-value for each datum, after creating the plot-widget.

(let* ((width 500)
       (height 400)
       (plot-widget
        (make-instance 'plot-widget
          :title "Two Sets of Points"
          :chart-objects '((:id first :label "First Collection")
                           (:id second :label "Second Collection"))
          :plot-view (make-instance 'plot-view
                       :icon-images '(:square :circle)
                       :draw-lines nil)
          :x-axis (make-instance 'plot-value-axis
                    :axis-label "The X Axis")
          :y-axis (make-instance 'plot-value-axis
                    :axis-label "The Y Axis")
          :chart-legend (make-instance 'chart-legend
                          :layout-orientation :one-column)
          :right-attachment :right
          :bottom-attachment :bottom
          :left 0 :top 0 :width width :height height))
       (dialog (make-window :example-plot
                 :class 'dialog
                 :title "Example Plot"
                 :scrollbars nil
                 :interior (make-box-relative 40 40 width height)
                 :dialog-items (list plot-widget)))
       item-id)

  ;; This version copies your data to the plot-widget,
  ;; one element at a time.
  (dotimes (object-index (array-dimension *points* 0))
    (dotimes (value-index (array-dimension *points* 1))
      (set-plot-value plot-widget
                      :object-index object-index
                      :value-index value-index
                      :x (aref *points* object-index value-index 0)
                      :y (aref *points* object-index value-index 1))))
  dialog)

The second version builds the same plot by supplying a plot-value-returner function rather than by calling set-plot-value in a loop. This technique is typically more efficient because it accesses your data where it already lives, rather than first accumulating the data gradually into the plot-widget's own internal representation.

When using a plot-value-returner, you generally need to also supply a list of chart-objects and a plot-values-max-index value, to tell the widget the range of indexes over which it should call your plot-value-returner function.

(let* ((width 500)
       (height 400)
       (plot-widget
        (make-instance 'plot-widget
          :title "Two Sets of Points"

          ;; This version uses a plot-value-returner function, along
          ;; with a plot-values-max-index and a list of chart-objects.
          :plot-value-returner
          (lambda (plot-widget value-type value-index
                               object-index object-id)
            (declare (ignore plot-widget object-index))
            (case value-type
              (:x (aref *points* object-index value-index 0))
              (:y (aref *points* object-index value-index 1))))
          :plot-values-min-index 0
          :plot-values-max-index (1- (array-dimension *points* 1))
          :chart-objects '((:id first :label "First Collection")
                           (:id second :label "Second Collection"))
          
          :plot-view (make-instance 'plot-view
                       :icon-images '(:square :circle)
                       :draw-lines nil)
          :x-axis (make-instance 'plot-value-axis
                    :axis-label "The X Axis")
          :y-axis (make-instance 'plot-value-axis
                    :axis-label "The Y Axis")
          :chart-legend (make-instance 'chart-legend
                          :layout-orientation :one-column)
          :right-attachment :right
          :bottom-attachment :bottom
          :left 0 :top 0 :width width :height height))
       (dialog (make-window :example-plot
                 :class 'dialog
                 :title "Example Plot"
                 :scrollbars nil
                 :interior (make-box-relative 40 40 width height)
                 :dialog-items (list plot-widget))))
  dialog)

Here's one more example to illustrate a few more options. This one adds a second pair of axes with a different scale for a second chart object. It also specifies some additional properties of the plot-widget and its secondary objects, to give you a feel for some of the other modifiable attributes. And it uses a mathematical function to generate plot values in a circular manner, rather than using real data as an application typically would.

(let* ((width 500)
       (height 600)
       (plot-widget
        (make-instance 'plot-widget
          :title "Doris and Hubert"
          :subtitle #.(format nil "This pointless subtitle will wrap ~
                          automatically to avoid clipping at the sides ~
                          of the plot-widget.")
          :subtitle-color dark-red
          :footnote "There's really no need for this footnote."

          ;; This plot uses a different pair of axes for each object.
          ;; The :x-axis and :y-axis values for :hubert tell the plot-widget
          ;; to use the axes on the right and top sides of the plot for that
          ;; object, rather than the default axes along the left and bottom
          ;; sides.  And the :label properties are printed in the legend
          ;; to tell the user which objects go with which axes.
          :chart-objects (vector (list :id :doris
                                       :label "Doris Lurple (left and bottom axes)")
                                 (list :id :hubert
                                       :label "Hubert Murple (right and top axes)"
                                       :x-axis 2
                                       :y-axis 2))

          ;; Unlike the previous examples, we specify :draw-lines as true here,
          ;; to draw lines between the plotted points.  True is the default.
          ;; All of the lines in the plot data will be 2 pixels thick.
          :plot-view (make-instance 'plot-view
                       :icon-images '(:square :circle)
                       :icon-fill-colors (list blue green)
                       :line-widths (list 2)
                       :draw-lines t)

          ;; Here we create the usual primary axes that lie along the
          ;; left and bottom sides of the plot.
          :x-axis (make-instance 'plot-value-axis
                    :axis-label "Lower Axis"
                    :axis-color blue
                    :range-bottom 0
                    :range-top 10)
          :y-axis (make-instance 'plot-value-axis
                    :axis-label "Left Axis"
                    :axis-color blue
                    :range-bottom 3.0
                    :range-top 7.0)

          ;; Since we're using the second pair of axes for this plot,
          ;; we specify some attributes for those axes here.
          :x-axis-2 (make-instance 'plot-value-axis
                      :axis-label "Upper Axis"
                      :axis-color (make-rgb :green 192)
                      :draw-minor-tics t
                      :draw-minor-labels nil
                      :minor-tic-length 4
                      :range-bottom 0
                      :range-top 10)
          :y-axis-2 (make-instance 'plot-value-axis
                      :axis-label "Right Axis"
                      :axis-color (make-rgb :green 192)
                      :draw-minor-tics t
                      :minor-tic-length 4
                      :range-bottom 0
                      :range-top 8
                      :major-tic-increment 2
                      :minor-tics-per-major-tic 2)

          :chart-legend (make-instance 'chart-legend
                          :legend-font (make-font-ex :swiss "Arial / ANSI" 12))

          ;; This is a rather artificial plot-value-returner because
          ;; it uses a mathematical function to generate points to plot,
          ;; rather than looking up arbitrary real-world data as usual.
          ;; But it illustrates returning the various attributes of a
          ;; plotted point, such as :x-high for an error-range value to
          ;; the right of a plotted point.
          :plot-value-returner
          (lambda (plot-widget value-type value-index
                               object-index object-id)
            (declare (ignore plot-widget object-id))
            (let* ((factor 2.4)

                   ;; We use some trigonometry here to generate
                   ;; values that go around in a circle.  The plot-widget
                   ;; isn't limited to laying out a sequence of items in
                   ;; a single direction as the chart-widget is.
                   (x (+ 5 (* (+ object-index 2)
                              (cos (* value-index factor)))))
                   (y (+ 5 (* (1+ object-index)
                              (sin (* value-index factor))))))

              ;; The keywords here are all of the possible values of the
              ;; value-type argument that is passed to a plot-value-returner
              ;; function.  :x and :y are the coordinates of a point to plot.
              ;; The low and high values are typically used for an error
              ;; range, and this plot-widget will draw them as T-bars.
              (case value-type
                (:x x)
                (:y y)
                (:x-low nil) ;; Don't draw any T-bars toward the left.
                (:x-high (* x 1.07))
                (:y-low (* y .95))
                (:y-high (* y 1.05))

                ;; Here we artificially use larger icons for plotted points
                ;; that are farther to the right (with a larger X coordinate).
                (:icon-size (ceiling (+ x 1))))))

          ;; This tells the plot-widget to call the plot-value-returner
          ;; function a number of times with the value-index argument
          ;; varying from 0 through 12.
          :plot-values-min-index 0
          :plot-values-max-index 12

          :right-attachment :right
          :bottom-attachment :bottom
          :left 0 :top 0 :width width :height height))
       (dialog (make-window :example-plot
                 :class 'dialog
                 :title "More Complete Example"
                 :scrollbars nil
                 :interior (make-box-relative 40 40 width height)
                 :dialog-items (list plot-widget))))
  dialog)

Copyright (c) 1998-2009, Franz Inc. Oakland, CA., USA. All rights reserved.
Documentation for Allegro CL version 8.1. This page is new in the 8.1 release.
Created 2007.4.30.

ToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 8.1
New since 8.1 release.