Training

Visual LISP: Chart Drawing

In this article, you learn how design and start programming an application. A good program requires structure, and a proper design saves you time in the long run. One problem with most AutoCAD® customization projects is that you must continue to produce engineering and architectural drawings while attempting to customize the system. You can program only in fits and starts, resulting in poorly structured programs or customizations that no one can really use.

Into the Fire!

How do you design a program? Most prefer to simply start writing code or at least attempting to write code right out of the gate. This approach may work well for simple programs and macros but often results in a menu system that looks like the inside of an Egyptian tomb, leading to an underutilized system and wasted time.

To design a program, look at the application in the simplest of terms. Write a single sentence or two describing the application. Don’t get bogged down in the details yet, but think of the application as large blobs that connect in a logical manner. To illustrate this concept, I am going to use an example from CAD Cruise 2003: how to draw a chart in the AutoCAD software application given data in a list.

The desired output is a table like that shown in Figure 1. The table is a simple example created using a set of test data and functions from the Scrolls and Potions section of the Wizards Lab, under Draw Chart.

Figure 1: Simple chart example.

Drawing a chart is not difficult, so let’s proceed to the next step in the design. Move a little closer to the problem to be solved. Think about how you would approach this problem by hand or using only AutoCAD command primitives. Determining the anchor point and the direction of flow for the creation of the chart is the first step.

When drawing a chart by hand, you typically select a corner point as the anchor. You then size up each column for width and each row for height. This somewhat tedious exercise is ideally performed by the computer.

For a computer, this drawing problem comes down to two basic things: data representation and basic anchor and flow control. The CAD Cruise design team decided to draw charts from the upper-left corner down. The reasoning was quite simple. New items could be added to the bottom of the data list, which would simply glow taller when charted. We discussed numerous chart uses and decided that this concept was easiest as well. The first row contains the headers, and following rows contain the data.

It is important to determine data requirements as early as possible in the design process. As the details of the data are discussed, numerous ideas will come and go. Write your ideas down so that you can reference the data format everyone has agreed to use. You can add new data to the list as needed during the design phase.

So let’s look at the data format we came up with on CAD Cruise. Let me make one thing perfectly clear: we were designing this program under the worst possible conditions. No, the seas were not tossing us about. The condition was a large and diverse committee. When too many cooks are involved in the design process, the results may get complicated.

The Data

The data list that is sent to the chart-generating program is a nested list of rows. Each row consists of a nested list of cells. The cells themselves are stored like an entity data list. The team determined that this format was the most flexible and enabled us to be somewhat creative in the chart-building process. Each cell can be uniquely formatted to some extent, as seen in the example Figure 1 where the header cells are drawn using a different text height.

The following listing is clipped from the scroll provided under Draw Chart in the Potions and Scrolls section of the Wizards Lab.

;; Data List Format
;;
;; ( ;;chart details
;; ( ;;first row of data
;;   ( ;;first cell details
;;     (1 . “<text display to>“)
;;     (40 . <text height>)
;;     (50 . <rotation>)
;;     ... other group code settings for text
;;   ) ;; end first cell
;;   ... more cells for row
;; ) ;;end first row
;; ... more rows with cells
;; ) ;;end of chart

The data is stored in rows. The first level of nesting in the list structure is a row of data. Inside each row is a cell. The cells are association lists, with the first member being an integer group code. Group code 1 corresponds to the text, 40 to the text height, 50 is the text rotation angle in radians, and so forth. The only group codes not supplied are the name of the entity type and the insertion point, codes 0 and 10 respectively. (The Visual LISP online documentation includes a complete list of group codes. Follow the links in the Contents to the DXF™ Reference details.)

Although the data is stored in rows, it is simple to manipulate data by column. After all, LISP does mean List Processing and can easily work with a complicated list.

For example, the following code extracts a column of data from the data list:

(setq COLUMN
  (mapcar
    ‘(lambda (X)
    (nth NN X))
  DataList))

Before you panic, the AutoLISP® subr mapcar is not as bad as it looks. This handy subr is designed for list processing. You give it a list or a group of lists as parameters, and it applies a function to each member of the list. The result is a list of all the results.

In the preceding code the mapcar subr extracts each item from DataList. At the upper level that means it is extracting a row from the data list. Each row is placed in the parameter X, which is then used with an nth subr to extract a specific member from the row list. NN is the column we want. If it is zero you get the first column in the data list as a result.

It’s easy to manipulate this list with just a few lines of code. The Visual LISP® programming language provides even more tools for list manipulation, such as foreach and apply. Each has special properties that are useful when working with list data structures. Examples of each are supplied in the program associated with this article.

The Algorithm

The following phrase aptly describes the design process we are following:

Data + Algorithms = Programs

An algorithm is a description of a process, a recipe to follow to achieve something. When combined with data, the result is a program.

Now it is time to apply an algorithm to our data list in order to get closer to our program. The algorithm starts as an odd collection of tidbits and details and eventually evolves into a cohesive structure. The tidbits are even more disjointed if a committee does the design! Once again, try to limit the design group to a few people who understand the system as well as the application. A larger group can review the design, and you can apply new input in the context of the entire design. For complex applications, you may need to repeat this process several times to get a truly good design.

Fortunately, this application is not complex. Unfortunately, it does have a variety of applications and thus no single solution will suite everyone. The CAD Cruise team decided to create a more humble chart program that demonstrated how to work with the data. Individual team members could then expand the program.

Our simple algorithm came down to the following steps:

1. Determine if the list is valid (it is a complex nested list and prone to errors!).

2. Determine the column height of the list. To do so, add the Y size of each cell in each column.

3. Find out where the Y values need to be for each row. To determine where the cell starts, look at each row to learn the maximum Y size for that row.

4. Create the chart in column order. Draw the vertical left line for the entire column, and then draw the top. For each cell, draw the text and bottom line. Use the data from the Y values list for the text and bottom line so that the entire chart lines up.

Keep in mind that there are many ways to write this program.

The next step is to break down these processes even further. But first, let’s look for things that seem to be common in the steps defined thus far.

One thing you’ll notice immediately is that we are frequently obtaining the size of a cell. In our notes about how to do something, we said that we must define a function to obtain the size of an individual cell. We also note that we need other data containing the Y values for the rows and text placements. And last, when working with lists in column order, we need a function to extract columns from the data list. The following sections contain the notes for these common routines. Italic text denotes pseudocode, or writing that is beginning to resemble actual program code.

The Size of the Cell

Given the text information as an entity list, the textbox subr can be employed to determine the bottom and upper corners of a box drawn around the text. Subtracting the X and Y values yields the size of the text in the cell. To avoid cramming the text into the cell and to provide maximum flexibility for presentation, a cell multiplication factor is applied to the X and Y values independently of each other.

Get textbox and save as TMP.
Return list that is (caadr TMP) - (caar TMP) for X size multiplied by factor for width of cell and (cadadr TMP) minus the (cadar TMP) for the Y size multiplied by a different factor.

Data List for Y Values

Each row should have the same baseline even if the individual cells are different in size. Need a list that describes each row height in terms of Y (from zero, going down) for the base of the line and the text point for left justified.

Read through all columns and get maximum Y size.
Read through all rows and get maximum Y size for each row, save in list for Y starting value of line.
Calculate Y start of text for row relative to the Y starting value.
Calculate only the first row X displacement and just copy that for all the other rows.

Read Through a Column and Get Y Size and Maximum X Size

This is needed in the preceding routine, so we have another note for it.

Given the data list and an index value, get the column details for that index. Reading through each cell, get the size and add the Y values to learn the total size of the column. Check the width to see if it is greater than the greatest value found thus far and, if so, replace the greatest value found thus far.

ColDetails = (Get_Column DataList NN)
WW = 0.0
HH = 0.0;
Loop through the cell details for the column
  Get cell size as (X,Y)
  TMP = (Cell_Size CellDetail)
  HH = HH + (cadr TMP)
  if (car TMP) > WW then
    WW = (car TMP)
Loop
list WW HH is returned

With these notes (and others), continue breaking down the program until you have a description of every detail using pseudocode or even a flow chart. Each equation is known, and all the required variables are laid out so that you can optimize the application when ready. The next step is a design walkthrough with others who were not involved in the design process. Or in the case of a simple application routine such as our chart-drawing function, you can just code it directly.

The result of my puttering with the algorithms and data structures can be found in the Potions and Scrolls section of the Wizards Lab. You are welcome to use that code as a foundation to create your own program for chart building with Visual LISP. I hope this tutorial has provided insight into the design process. By taking good notes, you can keep your programming project on track and even establish milestones for management purposes as your skills improve.