File: tutorial_plots.rb

package info (click to toggle)
ruby-tioga 1.14-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 10,508 kB
  • sloc: ansic: 39,112; ruby: 17,031; sh: 79; makefile: 29
file content (373 lines) | stat: -rw-r--r-- 18,833 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

module Tioga
module Tutorial

=begin rdoc

= The extras for making plots

As usual, I'll be assuming you've been working your way through the sections of the tutorial in order,
so by the time you get here, you're familiar with the basic structure of a tioga document and the
fundamentals of making figures with FigureMaker.  This section builds on that knowledge to add the tools needed for
making figures that are plots.  We'll use "plots.rb" as a source of examples. 

You'll find "plots.rb" file in the "samples/plots" folder.  Make a portfolio by entering

  tioga plots -p

Take a look to see what's there.

As you can see, the examples range from the simple ("Blues") to the complex ("Contours").  Along the way,
they illustrate ways of combining plots ("Side_by_Side", "Two_Ys", "Rows", "Columns", etc.), and
provide samples of various techniques for adding information to plots ("Legend_Inside", "Legend_Outside",
"Labels", "Error_Bars", "Arrows", and "Special_Y").  Finally, there are several cases where tioga includes
tools for creating things to be shown in the plots ("Sampled_Splines", "Steps", "Splines", and
"Sampled_Data").  

We'll go through some of the cases in some detail, and then let you pick and choose from the rest.  Rather than
attempting to wade through it all now, you might prefer to skim quickly to see what's there,
and come back later if you want to look at a particular piece of sample code for ideas.

---

link:images/blues.png

Let's start by loading "plots.rb" into a text editor and finding the definition for "Blues".  It looks
like this:

    def blues
        read_data
        t.do_box_labels('Blues Plot', 'Position',
            '\textcolor[rgb]{0,0,1}{Blues}')
        show_model_number
        xs = @positions
        ys = @blues
        t.show_plot(plot_boundaries(xs,ys,@margin,-1,1)) { 
            t.show_polyline(xs,ys,Blue) }
    end

The read_data routine is part of "plots.rb" and reads data -- big surprise -- and is discussed in a later section (Animation).
The do_box_labels method from the FigureMaker puts up the title, the xlabel, and the ylabel. Our own show_model_number
routine puts the model number in the upper right corner of the plot.   The read_data routine put the current Dvector values for the x positions and the corresponding y values for "blues" in local instance variables for us, and we're just copying them to local variables, "xs" and "ys", for convenience.  The actual creation of the
contents of the plot happens inside the call to show_plot.  The show_plot routine takes one argument, the array of
boundaries, and a block of code to do the work of creating the contents of the plot frame.

We'll go through show_plot in detail, but first a few words about frames and boundaries and coordinate systems.  Here's 
what it says in the introduction to the CoordinateConversions module.

[]  There are four different coordinate systems used in tioga.  The contents of the figure or plot
    are positioned using "figure" coordinates which correspond to the values along the x and y axes
    as determined by the boundary attributes.  Things like the title and axis labels are positioned using
    "frame" coordinates that run from (0, 0) at the lower left corner of the frame to (1, 1) at the upper right
    corner.  The frame is positioned on the page using "page" coordinates that run from (0, 0)
    at the lower left of the page to (1, 1) at the upper right.  The actual numbers used in PDF and TeX files
    are given in "output" coordinates which are simply scaled-up page coordinates (so that locations
    can be represented using small integers rather than floats).

In our case, the figure coordinates come from the "xs" and "ys" that were read from the data source.
The values of the coordinates at the edges of the frame are referred to as the "boundaries" of the plot,
and they get passed to show_plot in an array that is created by the plot_boundaries routine defined in "plots.rb".
Here's what it does.

     def plot_boundaries(xs, ys, margin, ymin=nil, ymax=nil)
  1      xmin = xs.min
  2      xmax = xs.max
  3      ymin = ys.min if ymin == nil
  4      ymax = ys.max if ymax == nil
  5      width = (xmax == xmin)? 1 : xmax - xmin
  6      height = (ymax == ymin)? 1 : ymax - ymin
  7      left_boundary = xmin - margin * width
  8      right_boundary = xmax + margin * width
  9      top_boundary = ymax + margin * height
 10      bottom_boundary = ymin - margin * height
 11      return [ left_boundary, right_boundary,
                  top_boundary, bottom_boundary ]
     end
    
The "xs" and "ys" given as arguments are Dvectors, and in lines 1 and 2 the Dvector min and max
methods are used to find the range of values covered by the current xs.  Lines 3 and 4 do the
same for the ys, but only if the values for ymin and ymax were not provided by the caller.
The "if ymin == nil" at the end of line 3 is an example of using a conditional qualifier at 
the end of statement.  In addition to "if <test>", you can say "unless <some other test>".
The notation "ymin=nil" in the argument list specifies that "ymin" is optional and "nil" is
its default value when the caller omits it.
In our case, we specified -1 for ymin and +1 for ymax, so those values will be used.
Lines 5 and 6 calculate the width and height ranges from the min and max values, and check
for the nasty case of min equal max.  The "?" notation here comes from C.  You can also write this as

    if (xmax == xmin)
        width = 1
    else
        width = xmax - xmin
    end

The "margin" argument to plot_boundaries lets the caller specify how much
room to leave on the edges around the data.  In our case, we passed the local attribute "@margin"
which our initialization routine set to 0.1.  The given margin and the calculated width and height
are used to determine the boundaries for the plot frame on lines 7 to 10.  These are packed into an array
and returned on line 11.

Now that we have the boundaries, let's look at what show_plot does with them and with the block of
plot commands that follows the argument list.  Here's our call on show_plot from blues:

        t.show_plot(plot_boundaries(xs,ys,@margin,-1,1)) { 
            t.show_polyline(xs,ys,Blue) }

And here's what it would look like if we skipped show_plot and called the next level of
routines instead (this is all that show_plot does after all):

  1      t.set_bounds(plot_boundaries(xs,ys,@margin,-1,1))
  2      t.context do
  3          t.clip_to_frame
  4          t.show_polyline(xs,ys,Blue)
  5      end
  6      t.show_plot_box

The set_bounds routine takes the boundary values we just calculated and sets a bunch of figure attributes
accordingly, things like xaxis_reversed, yaxis_reversed, default_text_height_dx, and default_text_height_dy.
Next the #context routine establishes a save/restore context for the code that will do the actual creation
of the plot contents.  It saves the current values of lots of figure attributes, yields
control to the block of code, and then restores values when the code finishes.  The first thing that
happens in the code is a call on clip_to_frame that adjusts the clipping region by intersecting the
current clipping region with the new frame.  Then our plotting code happens (line 4).
Because we're inside a "context", we can set the stroke_color to Blue and not have to
worry about a possible side-effect on a caller who had previously set the stroke_color to something else.
When we are done, #context will restore the old stroke_color for us.
Finally, after #context finishes, the show_plot_box routine is called to add the axes, labels, and such.
The call on show_plot simply packages all of that in a neat bundle for us.  (And it is a lovely thing that
Ruby makes it all possible with code blocks!)

Our simple little "blue" example shows the basic form that's used even in complex plots: get the data,
figure out the boundaries, invoke show_plot, and do the plotting calls inside the code block that follows
the show_plot argument list.  

---

Now we"ll take a quick look at some of the ways to combine subplots.  The key here is to be able to
take existing plot definitions that were written as complete, stand-alone plots, and embed them
without modification as subplots in a more complex configuration.  Here's an example.  We have
plots for "Reds" and "Greens" separately.  They look like this:

link:images/reds.png

link:images/greens.png

We might want to combine them "side-by-side" like this:

link:images/red_green_side_side.png

Here's the side-by-side code:

      def side_by_side
  1       read_data
  2       t.landscape
  3       t.do_box_labels('Side by Side', 'Position', nil)
  4       t.subplot('right_margin' => 0.5) { 
  5           t.yaxis_loc = t.ylabel_side = LEFT;
  6           t.right_edge_type = AXIS_LINE_ONLY; reds }
  7       t.subplot('left_margin' => 0.5) {
  8           t.yaxis_loc = t.ylabel_side = RIGHT;
  9           t.left_edge_type = AXIS_LINE_ONLY; greens }
 10       show_model_number
      end
    
We start on line 1 with the usual call to read the data.  On line 2, we change the aspect ratio
to landscape, and on line 3 we specify a title and an xlabel, leaving the ylabel "nil" for now.
Then come the #subplot calls. 
The first call to #subplot sets a right margin of 0.5 which means that the "reds" plot will go
in the left 50% of our frame.  Similarly, the second call sets a left margin of 0.5 meaning that
the "greens" plot will go in the right 50%.  The code block following the argument list
for each #subplot contains the commands to be executed.  These commands are carried out inside a call
on #context, so any changes they make to the state will be restored before subplot returns.
In the first, on lines 5 and 6, we specify that the y axis and ylabel will be on the left, and the
right edge will be displayed as a line only.  Then we simply call the "reds" plot routine we
defined previously, the same one that makes the "Reds" plot.  On lines 8 and 9, we move the
y axis to the other side and call the same "greens" routine that makes the "Greens" plot for us.
Finally, when the subplots are done, we call our show_model_number routine to put the model number
in the upper right corner.

---

Since the "Reds" and the "Greens" have the same x axis, we might also combine them in a single plot
with different y axes on the left and right -- like this:

link:images/two_ys_plot.png

And here's the corresponding code from "plots.rb":

    def two_yaxes
  1      read_data
  2      t.landscape
  3      t.do_box_labels('Same X, Different Y\'s', 'Position', nil)
  6      t.subplot { 
  7          t.yaxis_loc = t.ylabel_side = LEFT;
  8          t.right_edge_type = AXIS_HIDDEN; reds }
  9      t.subplot {
 10          t.yaxis_loc = t.ylabel_side = RIGHT;
 11          t.left_edge_type = AXIS_HIDDEN; greens }
 12      show_model_number
    end

The only changes from side-by-side are to omit the "margin" arguments to subplot
so that the plots will use the same frame, and "AXIS_HIDDEN" has replaced "AXIS_LINE_ONLY".

---

When there are several plots with the same x axis, a stack of rows is a common choice:

link:images/rgb_stack.png

This is the "rows" routine from "plots.rb" that does this one.

    def rows
  1      read_data
  2      t.landscape
  3      show_model_number
  4      t.do_box_labels('Blues, Reds, Greens', 'Position', nil)
  5      t.rescale(0.8)
  6      num_plots = 3
  7      t.subplot(t.row_margins('num_rows' => num_plots, 'row' => 1)) do
  8          t.xaxis_type = AXIS_WITH_TICKS_ONLY
  9          blues
 10      end
 11      t.subplot(t.row_margins('num_rows' => num_plots, 'row' => 2)) do 
 12          t.xaxis_type = AXIS_WITH_TICKS_ONLY
 13          t.top_edge_type = AXIS_HIDDEN
 14          reds
 15      end
 16      t.subplot(t.row_margins('num_rows' => num_plots, 'row' => 3)) do 
 17          t.top_edge_type = AXIS_HIDDEN
 18          greens
 19      end
    end
    
There are a few new features here.  On line 5, we do a #rescale to 80%. This changes the
scale of text and lines to 80% of their previous height and width.  Text and line widths
don't scale 1:1 with graphics in tioga.  When you shrink a plot to use it as a subplot, the
graphics might get smaller by 50%, but the text might only get smaller by 80%.  Another new
feature is the use of the row_margins routine, on lines 7, 11, and 16, to calculate the subplot margins for us.
In the various code blocks, we adjust the appearance of the horizontal axes so that the
numbers only show up on the bottom (lines 8, 12, 13, and 17).   After making those arrangements,
we can just call the existing routines to do the "blues", "reds", and "greens" plots (lines 9, 14, and 18).

---

In the previous cases, we've combined plots that each show a single set of data.  For the case of
several lines of data in a single plot, we'll want to add a legend saying what data goes with what
line style.  There are built-in commands to collect the information, and other commands for adding it to
the plot.  Here's a simple example, one version with the legend inside the frame, and another with
the legend on the right.

link:images/Legends.png

The routines for the two versions are naturally quite similar:

    def legend_outside
        read_data
        show_model_number
        t.show_plot_with_legend('legend_scale' => 1.3) { reds_blues }
    end
    
    def legend_inside
        read_data
        show_model_number
        t.show_plot_with_legend(
            'legend_scale' => 1.3,
            'legend_left_margin' => 0.7,
            'plot_right_margin' => 0) { reds_blues }
    end
    
The show_plot_with_legend routine does the work for us.
It takes a dictionary argument specifying the layout for the main plot and the
legend.  After reseting the legend information, it calls #subplot for the main
plot, then does a set_subframe for the legend, and calls show_legend to do the
actually plotting of the legend text and lines.  Defaults for the layout are stored
in the FigureMaker's dictionary, legend_defaults, that you can change as desired.  For our "legend_outside"
example, we just use the defaults.  For the "legend_inside" case, we override the
default for the left margin of the legend to place it 70% of the distance along the frame
width.  In both cases, we're calling the "reds_blues" routine to make the plot.  Here's what it does.

    def reds_blues
        read_data
        t.do_box_labels("Reds and Blues", "Position", "Values")
        boundaries = setup_lines(@positions, [@blues, @reds], -1, 1)
        xs = @positions
        t.show_plot('boundaries' => boundaries) do
            t.show_polyline(xs,@blues,Blue,'Blues')
            t.show_polyline(xs,@reds,Red,'Reds')
        end
        show_model_number
    end
    
It is written like a standard plotting routine, and in fact could be used for that if we didn't want a legend.
The main new items are the calls on "save_legend_info" which tell the system
to save the given text along with the current line formating attributes for later
use by show_legend.  The show_legend routine uses various attributes defining the
layout of the legend information to show the saved text and make the sample lines.


== Plot Styles

You may have noticed the file plot_styles.rb in the samples/plots directory.  It has a couple of routines for setting
the tioga attributes that determine plot formats.  In plots.rb itself, at the top of the file, you'll find a line that does

  require 'plot_styles'
  
Open plot_styles.rb in your text edit and let's take a look inside.  It defines a module called MyPlotStyles, and if we
check the plots.rb file again, we'll find that it does 'include MyPlotStyles'.  That means that within the MyPlots class
in plots.rb we can directly use any methods defined in MyPlotStyles.  Turns out that there are currently two such methods
(although you may want to add more later).  The first is called sans_serif_style, and as the name suggests it changes
a few attributes so that TeX will use sans serif fonts.  The second method in MyPlotStyles is set_default_plot_style,
and it gives values to all of the tioga attributes relevant to figures and plots.

You may want to add special styles of your own -- if you make something that might be of interest to others, 
let us know.

There are several options for how style methods are used.  One would be to call a style method from the
initialization routine for your plot class (such as at the start of MyPlots initialize, just after setting
@figure_maker).  If you take that approach, you don't need to have your own 'enter_page' function since the
default will do just fine.  An alternative that I often use is to have an enter_page function and call the
style method from there.  This is the method implemented in plots.rb currently.  At the end of the initialization
method, there's a line that defines our enter_page_function:

        t.def_enter_page_function { enter_page }

And then we define out enter_page as follows:

    def enter_page
        set_default_plot_style
        t.default_enter_page_function
    end

A final way to use the style methods is to call them directly from a plot definition.  This is
illustrated by the method reds.  It begins with this line:

    sans_serif_style unless t.in_subplot

This is doing a conditional style change.  If we use the Reds plot by itself, it switches to
sans serif, but if we combine it as part of "super-plot", then it uses whatever has already
been set up.  I don't mean to suggest that you start having lots of conditional style changes;
the point here is simply that styles can be set in various ways at various stages of creating a plot.

In all of this it is important to be clear about the order in which things happen.  First is the initialization method
that is called when the `new' method is called for the class you've defined in your file.  Next is the enter_page
function that is called whenever a figure definition is about to be called to make a pdf.  Last of all is the actual
figure definition itself.  Style parameters can be changed at each of these stages -- for example, most figures may use
the style set during initialization, but some of them may make changes.  NOTE: any changes made by a figure routine
go away when the figure is done; all the settings revert back to the values they were given at initialization.
In our example above where the 'Reds' plot used sans serif, that choice was in effect only during the remainder of
the creation of that plot -- no "side-effects" of building the Reds plot will carry over to the next plot to be made.

---

Now let's review the tools for reading and manipulating the data for plots -- next stop: Data.


=end

module Plots
end # module Plots

end # module Tutorial
end # module Tioga