Search code examples
perllegendrrdtoolrrd

How can I align the fields in the legend of an RRDtool graph with Perl?


I'm plotting graphs with Perl using RRDs/RRDtool. I'm able to generate a graph with a legend, but I'm struggling to align the fields in the legend.

The code I'm using is:

"COMMENT:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\\n",
"COMMENT:\t\t\t\t\t\t\tMinimum\t\t\tMaximum\t\t\tAverage\t\t\t\tCurrent\\n",
"COMMENT:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\\n",

"LINE2:e2gran#DF01D7:\t2GRAN\t\t\t\t",
"GPRINT:e2gmin:\t%6.3lf %s\t\t",
"GPRINT:e2gmax:\t%6.3lf %s\t\t",
"GPRINT:e2gaver:\t%6.3lf %s\t\t",
"GPRINT:e2glast:\t%6.3lf %s\\n",

"LINE2:e3gran#0000FF:\t3GRAN\t\t\t\t",
"GPRINT:e3gmin:\t%6.3lf %s\t\t",
"GPRINT:e3gmax:\t%6.3lf %s\t\t",
"GPRINT:e3gaver:\t%6.3lf %s\t\t",
"GPRINT:e3glast:\t%6.3lf %s\\n",

"LINE2:e4gran#FF8000:\t4GRAN\t\t\t\t",
"GPRINT:e4gmin:\t%6.3lf %s\t\t",
"GPRINT:e4gmax:\t%6.3lf %s\t\t",
"GPRINT:e4gaver:\t%6.3lf %s\t\t",
"GPRINT:e4glast:\t%6.3lf %s\\n",

"LINE2:e2gtran#FFFF00:\t2GTRAN\t\t\t",
"GPRINT:e2gtmin:\t%6.3lf %s\t\t",
"GPRINT:e2gtmax:\t%6.3lf %s\t\t",
"GPRINT:e2gtaver:\t%6.3lf %s\t\t",
"GPRINT:e2gtlast:\t%6.3lf %s\\n",

"LINE2:allregmax#FF0000:\tALL_REGIONS\t\t",
"GPRINT:allmin:%6.3lf%s\t\t",
"GPRINT:allmax:%6.3lf%s\t\t",
"GPRINT:allaver:%6.3lf%s\t\t",
"GPRINT:alllast:%6.3lf%s\\n",

"LINE3:wrongdata#000000:\\tINCOMPLETE DATA\\n",

The font used for the legend is Arial. The output looks like this:

Legend with fields not aligned in columns

While I'm aiming for something like this:

Legend with fields nicely aligned in columns

I have tried TEXTALIGN, fiddling with spaces and tabs, and checked the RRDtool docs and different tutorials, but I just can't figure this out.


Solution

  • You have two problems.

    The font

    The first problem is that the Arial font is proportional. Every glyph has a different width. That looks nice in a book, but doesn't work for reports.

    You need to use a monospaced font (like Courier New) for this to work at all.

    The tabs

    Your second problem are the \ts.

    If stuff is too wide, you might have one \t too much. That's why you are seeing things being one set of 8 spaces too far left. To fix this, don't use whitespace and tabs directly. The underlying sprintf has syntax to create columns.

    You can do %-20s to make a right-aligned column of width 20 characters that will always be filled up with spaces. You can also do % 15s to make a 15 character column that is left-aligned.

    If we put that into practice, we'll get:

    my @cols = (
        sprintf( '%-20s',     '2GRAN' ),
        sprintf( '% 15.3lf', 10_754 ),
        sprintf( '% 15.3lf', 48_964 ),
        sprintf( '% 15.3lf', 12_812 ),
    );
    
    print join '', @cols;
    

    This creates:

    2GRAN                     10754.000      48964.000      12812.000
    

    Now if we do multiple lines, it still looks nice.

    foreach my $row (
        [qw/ 2GRAN  10754 48964 12812 /],
        [qw/ ASDFLONGERSTUFF  123 4444444 12312313 /],
    )
    {
        CORE::say join '',
            sprintf( '%-20s',    $row->[0] ),
            sprintf( '% 15.3lf', $row->[1] ),
            sprintf( '% 15.3lf', $row->[2] ),
            sprintf( '% 15.3lf', $row->[3] );
    }
    
    __END__
    2GRAN                     10754.000      48964.000      12812.000
    ASDFLONGERSTUFF             123.000    4444444.000   12312313.000
    

    Remember that all this stuff that you showed in your question is also just Perl code. Part of this is from the question, and another part is from chat.

    sub process_all_regions { 
    my ($region, $start,$end,$description) = @_; 
    RRDs::graph "$img/$region-$description-$start-days.png", 
    "-s -$start d", 
    "-e -$end d", 
    #"-s -1$duration*86400", 
    "--font","TITLE:18:Arial", 
    "--font","AXIS:11:Arial", 
    "--font","LEGEND:14:Courier New",
     "COMMENT:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\\n",
     "COMMENT:\t\t\t\t\t\t\tMinimum\t\t\tMaximum\t\t\tAverage\t\t\t\tCurrent\\n",
     "COMMENT:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\\n",
     "LINE2:e2gran#DF01D7:\t2GRAN\t\t\t\t",
     "GPRINT:e2gmin:\t%6.3lf %s\t\t",
     "GPRINT:e2gmax:\t%6.3lf %s\t\t",
     "GPRINT:e2gaver:\t%6.3lf %s\t\t",
     "GPRINT:e2glast:\t%6.3lf %s\\n",
    

    All of those lines with the sprintf patterns are just arguments to graph(). You don't have to put the verbatim into your code. You can create them programmatically. So if you wanted to have the header the with the same column width as the data, you can just use sprintf yourself to construct that.

    sprintf('COMMENT:%s\\n', '-' x 80),
    sprintf('COMMENT:% 35s%15s%15s%15s\n', qw/Minimum Maximum Average Current/),
    sprintf('COMMENT:%s\\n', '-' x 80),