# htmlMaker - generate html webpages # # R code to generate html code for webpages, especially for batch # jobs where R is run in background mode. This is a quick hack to # get simple webpages with graphs, including simple clickable graphs, # it is not a web design tool. You will need to know some html to # do anything beyond the simplest webpages. # # For the record, there are other more sophisticated packages that # will allow you to do these (and more) things: S2HTML, SJAVA, R Orca. # # These programs can be run in interactive mode (RGui on Windows, # R on linux), sourcing this file, then executing (or sourcing) # your commands. Or, you can run in batch mode by putting your input # commands in a file (e.g. "Rterm --no-save < input.file" on Windows, # "R BATCH --no-save input.file" on linux). # # This code was developed while a visitor at the National Institutes # of Health (NIH/CIT/MSCL). Permanent address: # # John Nolan # Math/Stat Department # American University # 4400 Massachusetts Avenue, NW # Washington, DC 20016-8050 USA # # jpnolan@american.edu # http://academic2.american.edu/~jpnolan # # htmlPlot based on code by Landon Jensen (lsjensen@micron.com) # and M. D. Thuleen (MDTHULEEN@micron.com) # ################# Overview ############################################# # # The code builds webpages by a sequence of calls: # # (1) htmlPageBegin - opens the html output file and writes header stuff # (2) htmlLine - writes a single line of output to the file (you can insert # any html desired using this) # (3) htmlPageEnd - appends the closing html code # # The plot functions are: # htmlPlot for (x,y) plot # htmlBarPlot for a barplot # htmlBoxPlot for box plots of a multiple data sets # These plot functions work by # (1) creating a plot in a separate file from the data supplied. # These plot files are in PNG format and are written to the # same path as the one specified in htmlPageBegin. # (2) adding html code to the webpage to show the plot and optionally to # make it clickable. # # The plot functions allow extra arguments (using the "..." mechanism) # to be passed directly to the plot function. This allows a savvy user # to set plot regions size (xlim=,ylim=), axes labels (xlab=,ylab=), # color, etc. Any argument that doesn't conflict with the what this # code uses can be specified. # # There is support for generating simple tables. # htmlWriteTable - writes a vector or matrix of values # You can generate more complex tables manually with # htmlTableBegin - to start a table # htmlTableRow - to fill in each row of the table # htmlTableEnd - to end a table # # You can include any type of plot in a webpage, though only the # ones produced inside this package (htmlPlot, htmlBarPlot, htmlBoxPlot) # can be "clickable". (This is because a clickable graph must have a # , and this code can't generate that unless it knows # details about the plot - what the objects are in the plot and where # they are at the pixel level.) To include an arbitrary plot on a # webpage, use: # htmlPlotBegin # ... your plot commands here .... # htmlPlotEnd # # You can put multiple plots inside one webpage, but you must # use different filenames for different plots, whether # on the same or on a different webpage. (If you don't the plots will # be overwritten and the clickable regions won't work correctly.) # # There is no error checking, so be careful. Also, if you attempt to # do clever things with plot options, you are on your own. For # example, specifying xlim= or ylim= as plot options that are # then the values in the data will crop some points. The active regions # associated with those regions then will be nonsense. # ################# Version info ############################################# # # Version 1.0 Oct. 21, 2002 # First public release. # # Version 1.1 Nov. 13, 2002 # To speed up processing of large webpages, # the method of writing output was changed. # Version 1.0 used "write(text,file=...)", which apparently opens # the file, writes the line, and then closes it for each line of # output. Version 1.1 uses "file(...)" to open the file once # for each page (in htmlPageBegin), "writeLines(...)" to # write another line to the file (in htmlLine), and then # closes the page (in htmlPageEnd). # # Version 1.2 Jan. 8, 2003 # Add a optional argument plot.method in htmlPageBegin to # try to resolve problems producing graphs: # (a) in R vs S-Plus # (b) on different operating systems (Win vs linux) # (c) whether in interactive/GUI or batch mode # If you are running R in interactive mode, nothing # should need changing. If you are running R in batch # mode (where the png(...) command may fail) or under # S-Plus (where there is no exact png(...) equivalent), # see function htmlBeginPlot for an explanation. I have only # tested S-Plus in interactive mode (splus.exe) under Win 2000, # batch mode (spqe.exe) does not work. # # Version 1.2a Jan. 16, 2003 # Make plot.method argument mandatory in htmlPageBegin # # Version 1.2b Feb. 6, 2003 # Add comment argument to htmlPage begin to allow site or page # specific comment to be included invisibly in the html. # Add tr.options argument to htmlTableRow to allow options, e.g. background color, # in different rows of a table. # # Version 1.2c Mar. 20, 2003 # Add td.options field to htmlTableRow, to allow fancier tables. # ################################################################ htmlPageBegin <- function( path, filename, title="",head.options="", body.options="",plot.method=NULL,comment="") { # create new html file with opening html code, # return a "html page descriptor" that contains info about the file # that is used by other routines version.info = "1.2c" if (is.null(plot.method)) { cat("The new version of htmlMaker requires a value for plot.method in htmlPageBegin. Use\n", " plot.method='png' if running R in interactive mode\n", " plot.method='bitmap' if running R in batch mode\n", " plot.method='export.graph' if running S-Plus in interactive mode\n", "If you are not producing plots, the value is ignored. If you are plotting,\n", "you must specify how plots images are to be produced and saved.\n", "See the comments in function htmlPlotBegin for details and help.\n") return( NULL ) } full.filename=paste(path,filename,sep="") con <- file(full.filename,open="w") pg <- list(path=path,filename=filename, full.filename=full.filename,connection=con, plot.method=plot.method) htmlLine( pg, " ", append=FALSE ) htmlLine( pg, paste("",sep="" ) ) if (title != "") htmlLine( pg, paste("",title,"", sep="") ) if (head.options != "") htmlLine( pg, head.options ) htmlLine( pg, paste(" ",sep="")) return( pg ) } ################################################################ htmlPageEnd <- function( pg ) { # append closing html code to pg htmlLine(pg," ") close(pg$connection) invisible() } ################################################################ htmlLine <- function( pg, text, append=TRUE ) { # add a line of text to the webpage pg writeLines( text, con=pg$connection) invisible () } ################################################################ htmlPlotBegin <- function( pg, plot.filename, plot.size=c(600,300),img.options="" ){ # start generic plot wrapper on "pg" # In this function, plot.size is in pixels (default for png) # # Version 1.2 attempts to make this work in R on different operating systems # in interactive or batch mode, and in S-Plus (at least in interactive mode). # Goal is to produce a .png file for output. Original version did this # by calling png(...), then producing a plot, then calling dev.off(). # # The first problem we encountered was that when running in batch # mode under linux, png(...) didn't work (on my installation). # This is because png(...) uses X11 to produce the graphics file, # which is not available when running R in batch mode (on my installation). # # The second problem is that S-Plus requires you to produce the # plot first, then use export.graph(...) on the current plot. # # The "solution" here is not very elegant. The function # htmlPageBegin has an optional argument, called plot.method, # that is added to the information in "pg". The recognized types # and their meanings are: # plot.method = "png" means using R and directly calling png(...) # plot.method = "bitmap" means using R and using bitmap(...) to # have ghostscript produce the png file. Ghostscript (gs) # must be available on the machine and bitmap must know # how to find it. If it is not in the default search # path, you should use Sys.putenv("R_GSCMD"="filename") # before calling this routine to tell R where gs is. # plot.method = "export.graph" means using S-Plus. #cat("htmlPlotBegin, plot.method=",pg$plot.method,"\n") plot.full.filename <- paste(pg$path,plot.filename,sep="") if (pg$plot.method == "png") { # create plot in PNG format png(file=plot.full.filename, width=plot.size[1], height=plot.size[2]) } else if (pg$plot.method == "bitmap") { # png not working in this case, use bitmap() with values picked to # roughly match what png() uses. res <- 96 bitmap(file=plot.full.filename,type="png256",width=plot.size[1]/res, height=plot.size[2]/res,res=res,pointsize=12) } else if (pg$plot.method != "export.graph") cat("ERROR in htmlBeginPlot: unknown plot.method=",plot.method, " No plot produced.\n") # generate html code to display the plot htmlLine(pg, "") htmlLine( pg, paste("",sep="") ) invisible() } ################################################################ htmlPlotEnd <- function( pg, plot.filename, plot.size=c(8.5,5.5) ){ # end generic plot "wrapper", see htmlPlotBegin for info # In this function, plot.size is in inches (default for export.graph) if (pg$plot.method == "export.graph") { plot.full.filename <- paste(pg$path,plot.filename,sep="") export.graph(FileName=plot.full.filename, Name = guiGetCurrMetaDoc("GraphSheet"), Width=plot.size[1], Height=plot.size[2] ) } else { dev.off() } invisible() } ################################################################ htmlPlot <- function( pg, x, y, plot.filename, hyperlink=NULL, plot.size = c(600,400), active.region.size=5, img.options="border=0", ... ){ # # R function to produce an x-y plot for web viewing and the html code # to view this plot. If hyperlink is not NULL, each point has an # "active region" - if a user clicks on the active region associated # with the point (x[i],y[i]), then hyperlink[i] is followed. # # pg is a "html page descriptor" returned by htmlPageBegin # x,y are vectors of doubles containing the points to be plotted # plot.filename is the filename (without path) for the plot. The graphic is # put in the same directory/folder as the webpage (specified inside 'pg'). # # optional arguments: # hyperlink is a vector of strings specifying the hyperlink to # follow if the user clicks on point (x[i],y[i]). # If hyperlink=NULL, no regions are active for clicking and a non-interactive # plot is produced. # plot.size[1] is the width in pixels of the graph # plot.size[2] is the height in pixels of the graph # active.region.size is the size of the active region (in pixels) around each (x,y). # More precisely, if a=active.region.size, the rectangle with corners at # (x-a,y+a), (x-a,y-a), (x+a,y-a), (x+a,y+a) # img.options allow one to specify border, align, etc. and other options. # ... are optional arguments that are passed to plot() function. This allows # caller to specify xlim,ylim,xlab,ylab,type, etc. # if (is.null(hyperlink)) { full.options <- img.options } else { # insert "usemap=" for active regions map.name <- paste(plot.filename,".map",sep="") full.options <- paste(img.options," usemap='#",map.name,"'",sep="") } htmlPlotBegin( pg, plot.filename, plot.size=plot.size, img.options=full.options ) # produce the plot par(mar=c(4,4,4,4)) plot(x,y,...) usr <- par("usr") plt <- par("plt") htmlPlotEnd( pg, plot.filename ) if (! is.null(hyperlink)) { htmlLine(pg, "",) htmlLine(pg, paste("",sep="") ) x1pixels <- active.region.size x2pixels <- active.region.size y1pixels <- active.region.size y2pixels <- active.region.size dataxmin <- usr[1] dataxmax <- usr[2] dataymin <- usr[3] dataymax <- usr[4] plotxmin <- plt[1] plotxmax <- plt[2] plotymin <- plt[3] plotymax <- plt[4] datawidth <- dataxmax-dataxmin dataheight <- dataymax-dataymin ratioxmin <- dataxmin/plotxmin ratioxmax <- dataxmax/plotxmax ratioymin <- dataymin/plotymin ratioymax <- dataymax/plotymax pixelxmin <- dataxmin*plot.size[1]/ratioxmin pixelxmax <- dataxmax*plot.size[1]/ratioxmax pixelymin <- dataymin*plot.size[2]/ratioymin pixelymax <- dataymax*plot.size[2]/ratioymax pixelwidth <- pixelxmax-pixelxmin pixelheight <- pixelymax-pixelymin for(i in 1:length(x)) { px.coords <- htmlXYtoPixel( x[i], y[i], pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) x1 <- px.coords[1] y1 <- px.coords[2] htmlLine(pg, paste("x=",x[i],", y=",y[i],"",sep="") ) } htmlLine(pg, "") htmlLine(pg, "" ) } invisible() } ################################################################ htmlXYtoPixel <- function( x, y, pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) { # convert (x,y) data value to pixel coordinates inside current plot # Here is a sketch of the coordinates used # # point data coordinates pixel coordinates # A (dataxmin,dataymin) # B (dataxmin+datawidth,dataymin+dataheight) # C (pixelxmin,pixelymin) # # # C # y | B # | # l | # a | (x,y) # b | # e | # l | # | # | # A----------------------------------------------------- # x label # # Notes: # 1. pixelwidth is the number of pixels per 1 data unit vertically # 2. pixelheight is the number of pixels per 1 data unit horizontally # 3. data coordinates have normal mathematical directions, but pixel # coordinates do not. pixel x value increases from left to right # as usual, but the pixel y value INCREASES from top to bottom. # # x1 <- pixelwidth*(x-dataxmin)/datawidth x1 <- floor(x1+pixelxmin + .5) y1 <- pixelheight*(1-(y-dataymin)/dataheight) y1 <- floor(y1+pixelymin + .5) invisible(c(x1,y1)) } ################################################################ htmlBarPlot <- function( pg, height, plot.filename, colors=1, group.names=paste(1:length(height)), hyperlink=NULL, plot.size = c(600,400), active.region.size=5, img.options="border=1", ... ){ # # R function to produce a barplot for web viewing and the html code # to view this plot with each bar potentially a "clickable region". # If a user clicks on the active region for bar i, then hyperlink[i] is followed. # # pg is a "html page descriptor" returned by htmlPageBegin # height is a vector of doubles containing the heights of each box # plot.filename is the filename (without path) for the plot. The graphic is # put in the same directory/folder as the webpage (specified inside 'pg'). # # optional arguments # colors indicates the color of the bars (default 1=black) # group.names is a list of names for each of the groups in height # hyperlink is a vector of strings specifying the hyperlink to # follow if the user clicks on point (x[i],y[i]). # If hyperlink=NULL, no regions are active for clicking and a non-interactive # plot is produced. # plot.size[1] is the width in pixels of the graph # plot.size[2] is the height in pixels of the graph # active.region.size is the size of the active region (in pixels) above each # bar of zero height. # img.options allow one to specify border, align, etc. and other options. # ... are optional arguments that are passed to plot() function. This allows # caller to specify xlim,ylim,xlab,ylab,type, etc. # # create plot in PNG format plot.full.filename <- paste(pg$path,plot.filename,sep="") png(file=plot.full.filename, width=plot.size[1], height=plot.size[2]) par(mar=c(4,4,4,4)) barplot(height,col=colors,names.arg=group.names,...) usr <- par("usr") plt <- par("plt") dev.off() # generate html code to display the graph and define active regions htmlLine(pg, "") htmlLine(pg, "",) if (is.null(hyperlink)) { htmlLine( pg, paste("",sep="") ) } else { # create mapping for active regions map.name <- paste(plot.filename,".map",sep="") htmlLine(pg, paste("",sep="") ) htmlLine(pg, paste("",sep="") ) x1pixels <- active.region.size x2pixels <- active.region.size y1pixels <- active.region.size y2pixels <- active.region.size dataxmin <- usr[1] dataxmax <- usr[2] dataymin <- usr[3] dataymax <- usr[4] plotxmin <- plt[1] plotxmax <- plt[2] plotymin <- plt[3] plotymax <- plt[4] datawidth <- dataxmax-dataxmin dataheight <- dataymax-dataymin ratioxmin <- dataxmin/plotxmin ratioxmax <- dataxmax/plotxmax ratioymin <- dataymin/plotymin ratioymax <- dataymax/plotymax pixelxmin <- dataxmin*plot.size[1]/ratioxmin pixelxmax <- dataxmax*plot.size[1]/ratioxmax pixelymin <- dataymin*plot.size[2]/ratioymin pixelymax <- dataymax*plot.size[2]/ratioymax pixelwidth <- pixelxmax-pixelxmin pixelheight <- pixelymax-pixelymin delta <- 0.2 left <- -1.0 for(i in 1:length(height)) { # the i-th bar has lower left coordinates (left,0) and upper right coordinates # (left+1,height[i]). Convert these to ll=lower left pixel coordinates and # ur=upper right pixel coordinates left <- left + (1.0+delta) ll <- htmlXYtoPixel( left, 0.0, pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) ur <- htmlXYtoPixel( left+1.0, height[i], pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) # if a bar has 0 height, make sure there is some clickable region if ( abs(ur[2]-ll[2]) < active.region.size) { ur[2] <- ll[2] - active.region.size } htmlLine(pg, paste("",group.names[i],"",sep="") ) } htmlLine(pg, "") } htmlLine(pg, "" ) invisible() } ################################################################ htmlBoxPlot <- function( pg, xlist, plot.filename, show.x=FALSE, group.names=paste(1:length(xlist)), hyperlink=NULL, plot.size = c(600,400), active.region.size=5, img.options="border=0", ... ){ # # R function to produce a boxplot for web viewing and the html code # to view this plot with each box potentially a "clickable region". # If a user clicks on the active region for bar i, then hyperlink[i] is followed. # # pg is a "html page descriptor" returned by htmlPageBegin # xlist is a list of vectors of doubles containing the points to be plotted # (not a matrix, because in general each group can have different lengths) # The easiest way to do this is to use list(x1,x2,x3) in the call. # If only one data set is supplied, you still must use list(x1) format # (ugly, but it makes this code easier). # plot.filename is the filename (without path) for the plot. The graphic is # put in the same directory/folder as the webpage (specified inside 'pg'). # # optional arguments # group.names is a list of names for each of the groups in height # hyperlink is a vector of strings specifying the hyperlink to # follow if the user clicks on box i. # If hyperlink=NULL, no regions are active for clicking and a non-interactive # plot is produced. # plot.size[1] is the width in pixels of the graph # plot.size[2] is the height in pixels of the graph # active.region.size is the minimal size of the active region (in pixels) # for each box. # img.options allow one to specify border, align, etc. and other options. # ... are optional arguments that are passed to plot() function. This allows # caller to specify xlim,ylim,xlab,ylab,type, etc. # # create plot in PNG format plot.full.filename <- paste(pg$path,plot.filename,sep="") png(file=plot.full.filename, width=plot.size[1], height=plot.size[2]) par(mar=c(4,4,4,4)) boxplot.value <- boxplot(xlist,names=group.names,...) usr <- par("usr") plt <- par("plt") dev.off() # generate html code to display the graph and define active regions htmlLine(pg, "") htmlLine(pg, "",) if (is.null(hyperlink)) { htmlLine( pg, paste("",sep="") ) } else { # create mapping for active regions map.name <- paste(plot.filename,".map",sep="") htmlLine(pg, paste("",sep="") ) htmlLine(pg, paste("",sep="") ) x1pixels <- active.region.size x2pixels <- active.region.size y1pixels <- active.region.size y2pixels <- active.region.size dataxmin <- usr[1] dataxmax <- usr[2] dataymin <- usr[3] dataymax <- usr[4] plotxmin <- plt[1] plotxmax <- plt[2] plotymin <- plt[3] plotymax <- plt[4] datawidth <- dataxmax-dataxmin dataheight <- dataymax-dataymin ratioxmin <- dataxmin/plotxmin ratioxmax <- dataxmax/plotxmax ratioymin <- dataymin/plotymin ratioymax <- dataymax/plotymax pixelxmin <- dataxmin*plot.size[1]/ratioxmin pixelxmax <- dataxmax*plot.size[1]/ratioxmax pixelymin <- dataymin*plot.size[2]/ratioymin pixelymax <- dataymax*plot.size[2]/ratioymax pixelwidth <- pixelxmax-pixelxmin pixelheight <- pixelymax-pixelymin # loc gets the "stats" part of the value returned from the boxplot call # it contains location information about the boxes for each group loc <- boxplot.value$stats for(i in 1:length(xlist)) { # the i-th box has lower left coordinates (left,loc[2,i]) and upper right coordinates # (right,loc[4,i]). Convert these to ll=lower left pixel coordinates and # ur=upper right pixel coordinates left <- i - 0.4 right <- i + 0.4 ll <- htmlXYtoPixel( left, loc[2,i], pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) ur <- htmlXYtoPixel( right, loc[4,i], pixelwidth, pixelheight, dataxmin, dataymin, datawidth, dataheight, pixelxmin, pixelymin ) # make sure there is some clickable region size if (abs(ur[2]-ll[2]) < active.region.size ) ur[2] <- ll[2] - active.region.size htmlLine(pg, paste("",group.names[i], "",sep="") ) } htmlLine(pg, "") } htmlLine(pg, "" ) invisible() } ################################################################ htmlTableBegin <- function( pg, caption="", options="border") { # start an html table, default is no caption and standard border # can insert other options: border=n, bgcolor, cellpadding, cellspacing, width, height. htmlLine( pg, paste("",sep="") ) if (caption != "") htmlLine( pg, paste("",sep="") ) invisible () } ################################################################ htmlTableEnd <- function( pg ) { # end an html table htmlLine( pg, "
",caption,"
" ) invisible () } ################################################################ htmlTableRow <- function( pg, row.str, header.row = FALSE, tr.options="", td.options=rep("",length(row.str))) { # insert a row into an html table # table will look odd if some rows have diff. number of columns, or empty cells htmlLine( pg, paste(" ") ) for (i in 1:length(row.str)) { if (header.row) htmlLine( pg, paste(" ",row.str[i]," ",sep="" ) ) else htmlLine( pg, paste(" ",row.str[i]," ",sep="" ) ) } htmlLine( pg, "" ) invisible () } ################################################################ htmlWriteMatrix <- function( pg, x, digits, caption="", row.labels=TRUE, col.labels=TRUE,options="border" ) { # write a table of numeric values in a 2-dim. matrix or a column vector # digits is number of digits to the right of decimal to display # row.labels tells whether to show row labels # col.labels tells whether to show column labels (ignored if x is a column vector) # options are for table formatting - see htmlTableBegin htmlTableBegin( pg, caption=caption, options=options ) if (is.matrix(x)) { if (col.labels) { row.str <- formatC(1:ncol(x),digits=digits) if (row.labels) row.str <- c(" ",row.str) htmlTableRow( pg, row.str ) } for (i in 1:nrow(x)) { row.str <- formatC(x[i,],digits=digits,format="f") if( row.labels ) row.str <- c(formatC(i),row.str) htmlTableRow( pg, row.str ) } } else { # then x is not a matrix, assume it is a (column) vector for (i in 1:length(x)) { row.str <- formatC( x[i], digits=digits,format="f" ) if (row.labels) row.str <- c(formatC(i),row.str) htmlTableRow( pg, row.str) } } htmlTableEnd( pg ) invisible () }