Philip W. L. Fong <pwlfong@cs.uregina.ca>
$Date: 2005/09/19 20:00:41 $
Suppose we are to write a program that takes an input file containing two positive integers and generates an SVG image that shows the relative magnitudes of the numbers. To be specific, we would like to draw a rectangular bar containing two segments of different colors. The length of each segment should be proportional to the magnitude of a corresponding number.
Here is an example input file, with filename gensvg.txt
.
32 64
The program should generate the following image.
Let us put the bar into an SVG image of size 100 × 30. We also want to give a margin of 10 on each side of the image. Let us document these decisions into a number of constant declarations.
const unsigned IMG_WIDTH = 100; const unsigned IMG_HEIGHT = 30; const unsigned IMG_MARGIN = 10;
Given these numbers we then want to deduce the dimensions of the retangular bar.
const unsigned BAR_WIDTH = IMG_WIDTH - 2 * IMG_MARGIN; const unsigned BAR_HEIGHT = IMG_HEIGHT - 2 * IMG_MARGIN;
Basically, the width of the bar is the width of the image minus the margins on the two sides. The height of the bar is calculated in a similar formula.
With the dimensions fixed, let us get down to the generation
of the image. Every SVG image begins with a standard prolog, and ends with
a standard epilog. Let us write a pair of void
functions
that generates the prolog and epilog of our image.
#include <iostream> using namespace std; void header() { cout << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << endl << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"" << endl << " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl << "<svg width=\"" << IMG_WIDTH << "\" height=\"" << IMG_HEIGHT << "\">" << endl << "<title>Bar</title>" << endl; } void footer() { cout << "</svg>" << endl; }
The hardest job is to paint the two segments representing the
relative magnitudes of the input numbers. Suppose the two numbers
are m
and n
. Then the segment representing
m
should have the following length.
BAR_WIDTH * m / (m + n)
Basically, m / (m + n)
is a ratio representing
the proportion of the bar that should represent the magnitude of
m
. Multiplying this ratio by the width of the bar
(BAR_WIDTH
) gives us the length of the segment
representing m
.
The length of the segment representing the magnitude of n
can be easily obtained by subtracting the above quantity from
BAR_WIDTH
.
Here is a function that draws the two segments.
void bar(unsigned m, unsigned n) { unsigned m_width = (unsigned) (((double) m) / ((double) m + n) * BAR_WIDTH); unsigned n_width = BAR_WIDTH - m_width; cout << "<rect x=\"" << IMG_MARGIN << "\" y=\"" << IMG_MARGIN << "\"" << " width=\"" << m_width << "\" height=\"" << BAR_HEIGHT << "\"" << " style=\"stroke: none; fill: red;\" />" << endl; cout << "<rect x=\"" << IMG_MARGIN + m_width << "\" y=\"" << IMG_MARGIN << "\"" << " width=\"" << n_width << "\" height=\"" << BAR_HEIGHT << "\"" << " style=\"stroke: none; fill: green;\" />" << endl; }
We use double precision floating point arithmatic to compute the
ratio, and casting the result back to integer. We also have not
forgotten to skip over the margins when we specify the placement
of the segments. We have decided to use red to highlight the
segment representing m
, and green for n
.
We then put everything together, and obtain the following program.
// // gensvg.cpp // #include <iostream> using namespace std; // Constant declarations specifying the dimensions of the image and // the rectangular bar. const unsigned IMG_WIDTH = 100; const unsigned IMG_HEIGHT = 30; const unsigned IMG_MARGIN = 10; const unsigned BAR_WIDTH = IMG_WIDTH - 2 * IMG_MARGIN; const unsigned BAR_HEIGHT = IMG_HEIGHT - 2 * IMG_MARGIN; // Prolog for the SVG image void header() { cout << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << endl << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"" << endl << " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl << "<svg width=\"" << IMG_WIDTH << "\" height=\"" << IMG_HEIGHT << "\">" << endl << "<title>Bar</title>" << endl; } // Epilog for the SVG image void footer() { cout << "</svg>" << endl; } // Generation of the two segments void bar(unsigned m, unsigned n) { unsigned m_width = (unsigned) (((double) m) / ((double) m + n) * BAR_WIDTH); unsigned n_width = BAR_WIDTH - m_width; cout << "<rect x=\"" << IMG_MARGIN << "\" y=\"" << IMG_MARGIN << "\"" << " width=\"" << m_width << "\" height=\"" << BAR_HEIGHT << "\"" << " style=\"stroke: none; fill: red;\" />" << endl; cout << "<rect x=\"" << IMG_MARGIN + m_width << "\" y=\"" << IMG_MARGIN << "\"" << " width=\"" << n_width << "\" height=\"" << BAR_HEIGHT << "\"" << " style=\"stroke: none; fill: green;\" />" << endl; } // Read input and generate SVG image int main() { unsigned m, n; cin >> m; cin >> n; header(); bar(m, n); footer(); return 0; }
To compile the program, we type in the following command.
$ g++ gensvg.cpp -o gensvg
Let us execute the program to see if the right SVG tags are generated.
$ ./gensvg < gensvg.txt <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg width="100" height="30"> <title>Bar</title> <rect x="10" y="10" width="26" height="10" style="stroke: none; fill: red;" /> <rect x="36" y="10" width="54" height="10" style="stroke: none; fill: green;" /> </svg>
The output seems to look fine. Let us store it into a file
gensvg.svg
.
$ ./gensvg < gensvg.txt > gensvg.svg
Let us then rasterize the SVG image, so that we may check if the image looks as we have expected.
$ rsvg gensvg.svg gensvg.png
A PNG image file gensvg.png
is generated as a result.
To view this image, we fire up mozilla (or Netscape), point to
the File
menu, and select Open File
to
open the PNG image.
You can tell that the program is correct, because the red segment is half of the length of the gree segment, reflecting the relative magnitude of 32 and 64.
We could also use the command xv
to view the image.
$ xv &
You will notice that the background becomes black. What happens is that the image has a transparent background: we only draw the bar without filling the background. This does not happen in Mozilla because the browser assumes white as the default background color.
$Id: gensvg.html,v 1.1 2005/09/19 20:00:41 pwlfong Exp $