diff --git a/server/modules/graphics/business.pike b/server/modules/graphics/business.pike index 8b7db5ae..e98d376b 100644 --- a/server/modules/graphics/business.pike +++ b/server/modules/graphics/business.pike @@ -1,1102 +1,1104 @@ /* * Caudium - An extensible World Wide Web server * Copyright © 2000-2005 The Caudium Group * Copyright © 1994-2001 Roxen Internet Software * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // //! module: Business Graphics //! Draws various diagrams for data presentation purposes. //! inherits: module //! inherits: caudiumlib //! inherits: images.pike //! type: MODULE_PARSER | MODULE_LOCATION //! cvs_version: $Id$ // /* * Draws diagrams pleasing to the eye. * * Made by Peter Bortas and Henrik Wallin * in October 1997 */ constant cvs_version = "$Id$"; constant thread_safe=1; #include #include #define VOIDSYMBOL "\n" #define SEP "\t" inherit "module"; inherit "caudiumlib"; inherit "images"; constant module_type = MODULE_PARSER | MODULE_LOCATION; constant module_name = "Business Graphics"; constant module_doc = "The Business Graphics tag\n
" "Draws different kinds of diagrams.
" "

"
 "\n<diagram> (container)\n"
 "Options:\n"
 "  help           Displays this text.\n"
 "  type           Mandatory. Type of graph. Valid types are:\n"
 "                 sumbars, normsumbars, linechart,"
 " barchart,\n"
 "                 piechart and graph\n"
 #if constant(Image.JPEG.decode)
 "  background     Takes the filename of a pnm-, gif- or\n"
 "                 jpeg-image as input.\n"
 #else
 "  background     Takes the filename of a pnm image as input.\n"
 #endif
 "  width          Width of diagram image in pixels.\n"
 "                 (will not have any effect below 100)\n"
 "  height         Height of diagram image in pixels.\n"
 "                 (will not have any effect below 100)\n"
 "  fontsize       Height of text in pixels.\n"
 "  font           Name of the font used. This can be\n"
 "                 overridden in the legend-, axis- and\n"
 "                 names-tags.\n"
 "  namefont       Name of the font for the diagram name.\n"
 "  legendfontsize Height of legend text in pixels.\n"
 "                 fontsize is used if this is undefined.\n"
 "  name           Writes a name at the top of the diagram.\n"
 "  namecolor      The color of the name-text. Textcolor\n"
 "                 is used if this is not defined.\n"
 "  namesize       Height of the name text in pixels.\n"
 "                 Fontsize is used if this is undefined.\n"
 "  grey           Makes the default colors in greyscale.\n"
 
 "  3D             Render piecharts on top of a cylinder, takes"
 " the\n                 height in pixels of the cylinder as argument.\n"
 /* " tone         Do nasty stuff to the background.\n"
    " Requires dark background to be visible.\n" */
 "  eng            If present, numbers are shown like 1.2M.\n"
 "  neng           As above but 0.1-1.0 is written 0.xxx .\n"
 "  tonedbox       Creates a background shading between the\n"
 "                 colors assigned to each of the four corners.\n"
 "  center         (Only for pie) center=n centers the nth"
 " slice\n"
 "  rotate         (Only for pie) rotate=X rotate the pie"
 " X degrees.\n"
 "  turn           If given, the diagram is turned 90 degrees.\n"
 "                 (To make big diagrams printable)\n"
 "  voidsep        If this separator is given it will be used\n"
 "                 instead of VOID (This option can also\n"
 "                 be given i xnames and so on)\n"
 "  bgcolor        Use this background color for antialias.\n"
 "  notrans        If given, the bgcolor will be opaque.\n"
 
 // Not supported any more!     "  colorbg        Sets the color for the background\n"
 "  textcolor      Sets the color for all text\n"
 "                 (Can be overrided)\n"
 "  labelcolor     Sets the color for the labels of the axis\n"
 
 "  horgrid        If present a horizontal grid is drawn\n"
 "  vertgrid       If present a vertical grid is drawn\n"
 "  xgridspace     The space between two vertical grids in the\n"
 "                 same unit as the data.\n"
 "  ygridspace     The space between two horizontal grids in\n"
 "                 the same unit as the data.\n"
 
 "\n  You can also use the regular <img> arguments. They"
 " will be passed\n  on to the resulting <img> tag.\n\n"
 "The following internal tags are available:\n"
 "\n<data> (container) Mandatory.\n"
 "Tab and newline separated list of data values for the diagram."
 " Options:\n"
 "  separator      Use the specified string as separator instead"
 " of tab.\n"
 "  lineseparator  Use the specified string as lineseparator\n"
 "                 instead of newline.\n"
 "  form           Can be set to either row or column. Default\n"
 "                 is row.\n"
 "  xnames         If given, the first line or column is used as\n"
 "                 xnames. If set to a number N, N lines or columns\n"
 "                 are used.\n"
 "  xnamesvert     If given, the xnames are written vertically.\n" 
 "  noparse        Do not run the content of the tag through\n"
 "                 the RXML parser before data extraction is done.\n"
 "\n<colors> (container)\n"
 "Tab separated list of colors for the diagram. Options:\n"
 "  separator      Use the specified string as separator instead"
 " of tab.\n"
 "\n<legend> (container)\n"
 "Tab separated list of titles for the legend. Options:\n"
 "  separator      Use the specified string as separator instead"
 " of tab.\n"
 "\n<xnames> (container)\n"
 "Tab separated list of datanames for the diagram. Options:\n"
 "  separator      Use the specified string as separator instead"
 " of tab.\n"
 "  orient         If set to vert the xnames will be written"
 " vertically.\n"
 "\n<ynames> (container)\n"
 "Tab separated list of datanames for the diagram. Options:\n"
 "  separator      Use the specified string as separator instead"
 " of tab.\n"
 "\n<xaxis> and <yaxis> (tags)\n"
 "Options:\n"
 /* " name=        Dunno what this does.\n" */
 //I know!!! /Hedda
 "  start          Limit the start of the diagram at this"
 " quantity.\n"
 "                 If set to min the axis starts at the lowest"
 " value.\n\n"
 "  stop           Limit the end of the diagram at this"
 " quantity.\n"
 "  quantity       Name things represented in the diagram.\n"
 "  unit           Name the unit.\n"
 "
"; #ifdef BG_DEBUG mapping bg_timers = ([]); #endif //FIXME (Inte alltid VOID! #define VOIDCODE \ do { voidsep = m->voidseparator||m->voidsep||res->voidsep||"VOID"; } while(0) int loaded; void start(int num, object configuration) { if (!loaded) { string cdir = _cdir(); loaded = 1; if (get_dir(cdir)) foreach(get_dir(cdir), string file) rm(cdir+file); else if (!Stdio.mkdirhier(cdir)) report_warning ("BG: Cache directory "+ cdir+" can not be created.\n"); } } void stop() { string cdir = _cdir(); if (get_dir(cdir)) foreach(get_dir(cdir), string file) rm(cdir+file); } void create() { defvar( "location", "/diagram/", "Mountpoint", TYPE_LOCATION|VAR_MORE, "The URL-prefix for the diagrams." ); defvar( "maxwidth", 3000, "Limits:Max width", TYPE_INT, "Maximal width of the generated image." ); defvar( "maxheight", 1000, "Limits:Max height", TYPE_INT, "Maximal height of the generated image." ); defvar( "maxstringlength", 60, "Limits:Max string length", TYPE_INT, "Maximal length of the strings used in the diagram." ); defvar( "cachedir", "bgcache/", "Cache directory", TYPE_DIR|VAR_MORE, "The directory that will be used to store diagrams. This is " "relative to the argument cache directory." ); } string query_provides() { return "business_graphics"; } string itag_xaxis(string tag, mapping m, mapping res) { #ifdef BG_DEBUG bg_timers->xaxis = gauge { #endif int l=QUERY(maxstringlength)-1; res->xaxisfont = m->font || m->nfont || res->xaxisfont; if(m->name) res->xname = m->name[..l]; if(m->start) if (lower_case(m->start[0..2])=="min") res->xmin=1; else res->xstart = (float)m->start; if(m->stop) res->xstop = (float)m->stop; if(m->quantity) res->xstor = m->quantity[..l]; if(m->unit) res->xunit = m->unit[..l]; #ifdef BG_DEBUG }; #endif return ""; } string itag_yaxis(string tag, mapping m, mapping res) { #ifdef BG_DEBUG bg_timers->yaxis = gauge { #endif int l=QUERY(maxstringlength)-1; res->yaxisfont = m->font || m->nfont || res->yaxisfont; if(m->name) res->yname = m->name[..l]; if(m->start) if (lower_case(m->start[0..2])=="min") res->ymin=1; else res->ystart = (float)m->start; if(m->stop) res->ystop = (float)m->stop; if(m->quantity) res->ystor = m->quantity[..l]; if(m->unit) res->yunit = m->unit[..l]; #ifdef BG_DEBUG }; #endif return ""; } /* Handle and */ string itag_names(string tag, mapping m, string contents, mapping res, object id) { #ifdef BG_DEBUG bg_timers->names += gauge { #endif int l=QUERY(maxstringlength)-1; if(!m->noparse) contents = parse_rxml( contents, id ); string sep = m->separator || SEP; string voidsep; VOIDCODE; array foo; if( contents-" " != "" ) { if(tag=="xnames") { res->xnamesfont = m->font || m->nfont || res->xnamesfont; foo=res->xnames = contents/sep; if(m->orient) if (m->orient[0..3] == "vert") res->orientation = "vert"; else res->orientation="hor"; } else { foo=res->ynames = contents/sep; res->ynamesfont = m->font || m->nfont || res->ynamesfont; } } else return ""; for(int i=0; i and */ string itag_values(string tag, mapping m, string contents, mapping res, object id) { #ifdef BG_DEBUG bg_timers->values += gauge { #endif string voidsep; VOIDCODE; if(!m->noparse) contents = parse_rxml( contents, id ); string sep = m->separator || SEP; if( contents-" " != "" ) { if(tag=="xvalues") res->xvalues = Array.map( contents/sep, floatify, voidsep ); else res->yvalues = Array.map( contents/sep, floatify, voidsep ); } #ifdef BG_DEBUG }; #endif return ""; } string itag_data(string tag, mapping m, string contents, mapping res, object id) { #ifdef BG_DEBUG bg_timers->data += gauge { #endif string voidsep; VOIDCODE; string sep = m->separator || SEP; if (sep=="") sep=SEP; string linesep = m->lineseparator || "\n"; if (linesep=="") linesep="\n"; if(!m->noparse) contents = parse_rxml( contents, id ); if ((sep!="\t")&&(linesep!="\t")) contents = contents - "\t"; array lines = contents/linesep-({""}); array foo = ({}); array bar = ({}); int maxsize=0; if (sizeof(lines)==0) { res->data=({}); return 0; } #ifdef BG_DEBUG bg_timers->data_foo = gauge { #endif bar=allocate(sizeof(lines)); int gaba=sizeof(lines); for(int j=0; jmaxsize) maxsize=sizeof(foo); bar[j] = foo; } #ifdef BG_DEBUG }; #endif if (sizeof(bar[0])==0) { res->data=({}); return 0; } #ifdef BG_DEBUG bg_timers->data_bar = gauge { #endif if (m->form) if (m->form[0..2] == "col") { for(int i=0; idata=bar2; } else res->data=bar; else res->data=bar; #ifdef BG_DEBUG }; #endif if (m->xnames) if (!(int)(m->xnames)) m->xnames=1; else m->xnames=(int)(m->xnames); if ((m->xnames)&&(sizeof(res->data)>m->xnames)) { res->xnames=res->data[..m->xnames-1]; int j=sizeof(res->xnames[0]); mixed foo=allocate(j); for(int i=0; ixnames, i)-({VOIDSYMBOL}))*" "; res->xnames=foo; res->data=res->data[m->xnames..]; } if (m->xnamesvert) res->orientation = "vert"; #ifdef BG_DEBUG bg_timers->data_gaz = gauge { #endif mixed b; mixed c; bar=res->data; int basonk=sizeof(bar); for(int i=0; idata=bar; #ifdef BG_DEBUG }; #endif #ifdef BG_DEBUG }; #endif return 0; } string itag_colors(string tag, mapping m, string contents, mapping res, object id) { if(!m->noparse) contents = parse_rxml( contents, id ); string sep = m->separator || SEP; res->colors = map(contents/sep, Colors.parse_color); return ""; } string itag_legendtext(string tag, mapping m, string contents, mapping res, object id) { int maxlen = QUERY(maxstringlength)-1; string voidsep; VOIDCODE; if(!m->noparse) contents = parse_rxml( contents, id ); string sep = m->separator || SEP; res->legendfont = m->font || m->nfont || res->legendfont; res->legend_texts = contents/sep; array foo = res->legend_texts; for(int i=0; iSyntax error  " "(<diagram help></diagram> gives help)

" + error + "


"; } //mapping(string:mapping) cache = ([]); mapping(string:object) palette_cache = ([]); int datacounter = 0; string quote(mapping in) { // Don't try to be clever here. It will break threads. /* string out; cache[ out = sprintf("%d%08x%x", ++datacounter, random(99999999), time(1)) ] = in; */ //NU: Create key string data=encode_value(in); string out=replace(Caudium.http_encode_string(MIME.encode_base64(Caudium.Crypto.hash_sha(data),1)), "/", "$"); string cdir = _cdir(); if (file_stat(cdir+out)) return out; //NU: Create the file Stdio.write_file(cdir+out, data); return out; } constant _diagram_args = ({ "xgridspace", "ygridspace", "horgrid", "size", "type", "3d", "templatefontsize", "fontsize", "tone", "background","colorbg", "subtype", "dimensions", "dimensionsdepth", "xsize", "ysize", "fg", "bg", "orientation", "xstart", "xstop", "ystart", "ystop", "data", "colors", "xnames", "xvalues", "ynames", "yvalues", "axcolor", "gridcolor", "gridwidth", "vertgrid", "labels", "labelsize", "legendfontsize", "legendfont", "tonedbox", "tunedbox", "legend_texts", "labelcolor", "axwidth", "linewidth", "center", "rotate", "image", "bw", "eng", "neng", "xmin", "ymin", "turn", "notrans", "colortable_cache"}); -constant diagram_args = mkmapping(_diagram_args,_diagram_args); + +mapping diagram_args = mkmapping(_diagram_args,_diagram_args); constant _shuffle_args = ({ "dimensions", "dimensionsdepth", "ygridspace", "xgridspace", "xstart", "xstop", "ystart", "ystop", "colors", "xvalues", "yvalues", "axwidth", "xstor", "ystor", "xunit", "yunit", "fg", "bg", "voidsep" }); -constant shuffle_args = mkmapping( _shuffle_args, _shuffle_args ); + +mapping shuffle_args = mkmapping( _shuffle_args, _shuffle_args ); string tag_diagram(string tag, mapping m, string contents, object id, object f, mapping defines) { int l=QUERY(maxstringlength)-1; contents=replace(contents, "\r\n", "\n"); contents=replace(contents, "\r", "\n"); #ifdef BG_DEBUG bg_timers->names = 0; bg_timers->values = 0; bg_timers->data = 0; #endif mapping(string:mixed) res=([]); #ifdef BG_DEBUG bg_timers->all = gauge { #endif if(m->help) return module_doc; if(m->colortable_cache) res->colortable_cache=m->colortable_cache; if(m->type) res->type = m->type; else return syntax("You must specify a type for your table.
" "Valid types are: " "sumbars, " "normsumbars, " "linechart, " "barchart, " "piechart and " "graph"); if(m->background) res->background = combine_path( dirname(id->not_query), (string)m->background); if (m->name) { res->name=m->name[..l]; if (m->namesize) res->namesize=(int)m->namesize; if (m->namecolor) res->namecolor=Colors.parse_color(m->namecolor); else res->namecolor=Colors.parse_color(defines->fg); } res->voidsep = m->voidseparator || m->voidsep; res->font = m->font || m->nfont; if(m->namefont) res->namefont=m->namefont; if (m->tunedbox) m->tonedbox=m->tunedbox; if(m->tonedbox) { array a = m->tonedbox/","; if(sizeof(a) != 4) return syntax("tonedbox must have a comma separated list of 4 colors."); res->tonedbox = map(a, Colors.parse_color); } else if (m->colorbg) res->colorbg=Colors.parse_color(m->colorbg); if ((m->bgcolor)&&(m->notrans)) { res->colorbg=Colors.parse_color(m->bgcolor); m_delete(m, "bgcolor"); } else if (m->notrans) res->colorbg=Colors.parse_color("white"); res->drawtype="linear"; switch(res->type[0..3]) { case "pie": case "piec": res->type = "pie"; res->subtype="pie"; res->drawtype = "2D"; break; case "bar": case "bars": case "barc": res->type = "bars"; m_delete( res, "drawtype" ); break; case "line": res->type = "line"; break; case "norm": res->type = "norm"; break; case "grap": res->type = "graph"; res->subtype = "line"; break; case "sumb": res->type = "sumbars"; //res->subtype = ""; break; default: return syntax("\""+res->type+"\" is an FIX unknown type of diagram\n"); } if(m["3d"]) { res->drawtype = "3D"; if( lower_case(m["3d"])!="3d" ) res->dimensionsdepth = (int)m["3d"]; else res->dimensionsdepth = 20; } Caudium.parse_html(contents, ([ "xaxis":itag_xaxis, "yaxis":itag_yaxis ]), ([ "data":itag_data, "xnames":itag_names, "ynames":itag_names, "xvalues":itag_values, "yvalues":itag_values, "colors":itag_colors, "legend":itag_legendtext ]), res, id ); if ( !res->data || !sizeof(res->data)) return syntax("No data for the diagram"); res->bg = Colors.parse_color(m->bgcolor || defines->bg || "white"); res->fg = Colors.parse_color(m->textcolor || defines->fg || "black"); if(m->center) res->center = (int)m->center; if(m->eng) res->eng=1; if(m->neng) res->neng=1; res->fontsize = (int)m->fontsize || 16; res->legendfontsize = (int)m->legendfontsize || res->fontsize; res->labelsize = (int)m->labelsize || res->fontsize; if(m->labelcolor) res->labelcolor=Colors.parse_color(m->labelcolor || defines->fg); res->axcolor = Colors.parse_color(m->axcolor || defines->fg); res->gridcolor = Colors.parse_color(m->gridcolor || defines->fg); res->linewidth = m->linewidth || "2.2"; res->axwidth = m->axwidth || "2.2"; if(m->rotate) res->rotate = m->rotate; if(m->grey) res->bw = 1; if(m->width) { if((int)m->width > QUERY(maxwidth)) m->width = (string)QUERY(maxwidth); if((int)m->width < 100) m->width = "100"; } else if(!res->background) m->width = "350"; if(m->height) { if((int)m->height > QUERY(maxheight)) m->height = (string)QUERY(maxheight); if((int)m->height < 100) m->height = "100"; } else if(!res->background) m->height = "250"; if(!res->background) { if(m->width) res->xsize = (int)m->width; else res->xsize = 400; // A better algo for this is coming. if(m->height) res->ysize = (int)m->height; else res->ysize = 300; // Dito. } else { if(m->width) res->xsize = (int)m->width; if(m->height) res->ysize = (int)m->height; } if(m->tone) res->tone = 1; if(!res->xnames) if(res->xname) res->xnames = ({ res->xname }); if(!res->ynames) if(res->yname) res->ynames = ({ res->yname }); if(m->gridwidth) res->gridwidth = m->gridwidth; if(m->vertgrid) res->vertgrid = 1; if(m->horgrid) res->horgrid = 1; if(m->xgridspace) res->xgridspace = (int)m->xgridspace; if(m->ygridspace) res->ygridspace = (int)m->ygridspace; if (m->turn) res->turn=1; m -= diagram_args; // Start of res-cleaning res->textcolor = res->fg; res->bgcolor = res->bg; m_delete( res, "voidsep" ); if (res->xstop) if(res->xstart > res->xstop) m_delete( res, "xstart" ); if (res->ystop) if(res->ystart > res->ystop) m_delete( res, "ystart" ); res->labels = ({ res->xstor, res->ystor, res->xunit, res->yunit }); if(res->dimensions) res->drawtype = res->dimensions; if(res->dimensionsdepth) res["3Ddepth"] = res->dimensionsdepth; if(res->ygridspace) res->yspace = res->ygridspace; if(res->xgridspace) res->xspace = res->xgridspace; if(res->orientation) res->orient = res->orientation; if((int)res->xstart) res->xminvalue = (float)res->xstart; if((int)res->xstop) res->xmaxvalue = (float)res->xstop; if(res->ystart) res->yminvalue = (float)res->ystart; if(res->ystop) res->ymaxvalue = (float)res->ystop; if(res->colors) res->datacolors = res->colors; if(res->xvalues) res->values_for_xnames = res->xvalues; if(res->yvalues) res->values_for_ynames = res->yvalues; if((int)res->linewidth) res->graphlinewidth = (float)res->linewidth; else m_delete( res, "linewidth" ); if((int)res->axwidth) res->linewidth = (float)res->axwidth; res -= shuffle_args; m->src = QUERY(location) + quote(res) + ".gif"; if ((res->name)&&(!m->alt)) m->alt=res->name; if (res->turn) { int t; t=m->width; m->width=m->height; m->height=t; } #ifdef BG_DEBUG }; #endif #ifdef BG_DEBUG if(id->prestate->debug) return(sprintf("
Timers: %O\n
", bg_timers) + Caudium.make_tag("img", m, id->misc->is_xml)); #endif if(m->tonedbox) m_delete(m, "tonedbox"); return Caudium.make_tag("img", m, id->misc->is_xml); } mapping query_container_callers() { return ([ "diagram" : tag_diagram ]); } int|object PPM(string fname, object id) { if( objectp(fname) ) perror("fname: %O\n",indices(fname)); string q; q = id->conf->try_get_file( fname, id ); if(!q) perror("Diagram: Unknown image '"+fname+"'\n"); object g; if (sizeof(indices( g=Gz ))) if (g->inflate) catch { q = g->inflate()->inflate(q); }; if(q) { object img_decode; #if constant(Image.ANY.decode) if (!catch{img_decode = Image.ANY.decode(q);}) return img_decode; else #endif if (q[0..2] == "GIF") if (catch{img_decode = Image.GIF.decode(q);}) return 1; else return img_decode; #if constant(Image.JPEG.decode) else if (search(q[0..13],"JFIF")!=-1) if (catch{img_decode = Image.JPEG.decode(q);}) return 1; else return img_decode; #endif else if (q[0..0]=="P") if (catch{img_decode = Image.PNM.decode(q);}) return 1; else return img_decode; #if constant(Image.JPEG.decode) perror("Diagram: Unknown image type for '"+fname+"', " "only GIF, jpeg and pnm is supported.\n"); return 1; #else perror("Diagram: Unknown image type for '"+fname+"', " "only pnm is supported.\n"); return 1; #endif } else return 1; } mapping http_img_answer( string msg ) { return Caudium.HTTP.string_answer( msg ); } mapping unquote( string f ) { //NU: Load the file f if (catch { return decode_value(Stdio.read_file(_cdir() + f)); }) return 0; // return cache[ f ]; } mapping find_file(string f, object id) { #ifdef BG_DEBUG //return 0; #endif //NU: If the file .gif exists return it string temp; string cdir = _cdir(); if (temp=Stdio.read_file(cdir+f+".gif")) return Caudium.HTTP.string_answer(temp, "image/gif"); if (f[sizeof(f)-4..] == ".gif") f = f[..sizeof(f)-5]; if( f=="" ) return http_img_answer( "This is BG's mountpoint." ); mapping res = copy_value( unquote( f ) ); //FIXME Ta bort f? if(!res) return http_img_answer( "Please reload this page." ); if(id->prestate->debug) return Caudium.HTTP.string_answer( sprintf("
%O\n", res) );
   
   mapping(string:mixed) diagram_data;
 
   array back=0;
   if (res->bgcolor)
     back = res->bgcolor;
 
   if(res->background)
   {
     m_delete( res, "bgcolor" );
     res->image = PPM(res->background, id);
 
     /* Image was not found or broken */
     if(res->image == 1) 
     {
       res->image=get_font(0, 24, 0, 0,"left", 0, 0);
       if (!(res->image))
 	throw(({"Missing font or similar error!\n", backtrace() }));
       res->image=res->image->
 #if constant(Image.JPEG.decode)
 	write("The file was", "not found ",
 	      "or was not a","jpeg-, gif- or","pnm-picture.");
 #else
 	write("The file was","not found ",
 	      "or was not a","pnm-picture.");
 #endif
     }
   } else if(res->tonedbox) {
     m_delete( res, "bgcolor" );
     res->image = Image.Image(res->xsize, res->ysize)->
       tuned_box(0, 0, res->xsize, res->ysize, res->tonedbox);
   }
   else if (res->colorbg)
   {
     back=0; //res->bgcolor;
     m_delete( res, "bgcolor" );
     res->image = Image.Image(res->xsize, res->ysize, @res->colorbg);
   } 
   /*else if (res->notrans)
     {
       res->image = image(res->xsize, res->ysize, @res->bgcolor);
       m_delete( res, "bgcolor" );
     }
   */
   if(res->font)
     res->font = resolve_font(res->font);
   else
     res->font = resolve_font("default");
   
   diagram_data = res;
   Image.Image img;
 
   if(res->image)
     diagram_data["image"] = res->image; //FIXME: Why is this here?
 
 #ifdef BG_DEBUG
   bg_timers->drawing = gauge {
 #endif
     switch(diagram_data->type) {
     case "pie":
       img = Graphics.Graph.pie(diagram_data);
       break;
     case "bars":
       img = Graphics.Graph.bars(diagram_data);
       break;
     case "sumbars":
       img = Graphics.Graph.sumbars(diagram_data);
       break;
     case "norm":
       img = Graphics.Graph.norm(diagram_data);
       break;
     case "line":
       img = Graphics.Graph.line(diagram_data);
       break;
     case "graph":
       img = Graphics.Graph.graph(diagram_data);
       break;
     }
 #ifdef BG_DEBUG
   };
   if (diagram_data->bg_timers)
     bg_timers+=diagram_data->bg_timers;
 #endif
   object ct;
   if(res->colortable_cache)
   {
     ct = palette_cache[res->colortable_cache];
     if(!ct)
       ct = palette_cache[res->colortable_cache] =
 	   Image.Colortable(img)->nodither();
   }
 
   if (res->turn)
     img=img->rotate_ccw();
 	
 #ifdef BG_DEBUG
   if(id->prestate->debug)
     werror("Timers: %O\n", bg_timers);
 #endif
   if(!ct) ct = Image.Colortable(img)->nodither();
 
   //NU: Save the created gif as .gif!
 
   if(back)
   {
     string foo=Image.GIF.encode(img, ct, @back);
     Stdio.write_file(cdir+f+".gif", foo);
     return Caudium.HTTP.string_answer(foo, "image/gif");
   }
   else
   {
     string foo=Image.GIF.encode(img, ct);
     Stdio.write_file(cdir+f+".gif", foo);
     return Caudium.HTTP.string_answer(foo, "image/gif");
   }
 }
 
 protected string _cdir() {
   return Stdio.append_path(caudium->QUERY(cachedir), "args", QUERY(cachedir)) + "/";
 }
 
 /* START AUTOGENERATED DEFVAR DOCS */
 
 //! defvar: location
 //! The URL-prefix for the diagrams.
 //!  type: TYPE_LOCATION|VAR_MORE
 //!  name: Mountpoint
 //
 //! defvar: maxwidth
 //! Maximal width of the generated image.
 //!  type: TYPE_INT
 //!  name: Limits:Max width
 //
 //! defvar: maxheight
 //! Maximal height of the generated image.
 //!  type: TYPE_INT
 //!  name: Limits:Max height
 //
 //! defvar: maxstringlength
 //! Maximal length of the strings used in the diagram.
 //!  type: TYPE_INT
 //!  name: Limits:Max string length
 //
 //! defvar: cachedir
 //! The directory that will be used to store diagrams. This is relative to the argument cache directory.
 //!  type: TYPE_DIR|VAR_MORE
 //!  name: Cache directory
 //