<% ' 'Module: includeGraph.asp 'Description: One-shot graphing include ' (hauls in expanded versions of other includes) 'Basis: GetFileContent("includeGraphOnly.asp") & _ ' GetFileContent("includeArray.asp") & _ ' GetFileContent("includeMergesort.asp") & _ ' GetFileContent("includeReport.asp") ' ' 'Module: includeGraphOnly.asp 'Description: Generalized graphing include 'Author: James Barbetti, 10-Nov-2000 'Style Notes: No subs are used. Everything is a function. ' Using functions makes it easier to call ' them via the EVAL function. 'History: ' ' WHILE AT GMDI ' '+ 10-Nov-2000 Plotting of X/Y Scatter Graphs '+ 12-Nov-2000 Frequency histograms and plots '+ 13-Nov-2000 X and Y axes (limited date support though) ' Additional comments explaining each function '+ 14-Nov-2000 Support for multiple-series graphing: ' ThreeDBarPlot, quickSeriesPlot. ' Support for line-graphs. ' Some explanatory comments. ' Numerous minor bug fixes. ' ' AS A HOME PROJECT ' '+ 07-Feb-2001 Added support for side-by-side "bar" graphs, ' of multiple series. ' Added support for area and stacked area ' graphs. ' Added support for pie graphs (single X value ' only so far. Later version you will be able ' to supply additional X values). '+ 08-Feb-2001 Now renders with Netscape 4.7 ' + Now supports rendering of tiled or static images ' within arbitary areas, using offsetPictureAttr ' when calculating DIV attributes (in IE anyway). ' + Corrected "horizontal line" bug in plotPieSlice. ' - Still doesn't line up tiles for Netscape. ' Removed ThreeDBarPlot ' + Draws (grey?) grid lines when drawing axes, if ' either the graphImplyXGrid or graphImplyYGrid ' variables are set to true. ' + See also graphGridColorExpr. '+ 09-Feb-2001 Now renders with Netscape 6 ' + Corrected bugs in pie charting drawing logic ' + Pie charts work when there are multiple X values ' (separate chart for each X value) ' + Limited support for series labeling in pie charts ' (i) labels "inside", and (ii) labels "outside" ' the pie slices. '+ 10-Feb-2001 You may now ' + Supply strings instead of numeric values for ' both the X and Y axes. ' - However, the strings will be displayed in ' ascending collation order, NOT in the order ' they appeared in the input array. ' - This problem IS fixable (by allowing the ' caller to supply additional columns), but I ' haven't implemented the fix yet. 'Todo: ' ' + add: writeGridWatermark(img) ' to draw a watermark image behind the grid. ' problem this time is the space used up by the rendering of the legend. ' + change: formatting for legends and point values (it should be user settable) ' - change: low bar values can appear below Y axis. Shouldn't be. ' - change: fix offset-image plotting in Netscape 4.7 and ' 6.0. This still does not work at all. ' + add: support for multiple series columns (where one is ' the series sort value and the other the HTML to ' display for the series). ' + add: support for additional X and Y columns (to give ' the text to display along the X or Y axis). ' 'Graph Settings ' - Note that the graphWidth,graphLeft, etc. settings ' are for the data to be plotted. They do not include ' overhead for plotting axes to left and below. ' Nor do they include overhead for plotting the series ' names etc. below the plotting area. ' dim graphXOrigin 'if true, force X origin to be zero dim graphYOrigin 'if true, force Y origin to be zero dim graphWidth 'the width of the plotting area in pixels dim graphHeight 'the height of the plotting area in pixels dim graphLeft 'the left of the plotting area dim graphTop 'the top of the plotting area dim graphXAxis 'True if X-Axis is to be displayed dim graphYAxis 'True if Y-Axis is to be displayed dim graphSeriesBoxWidth 'width for boxes shown for colors of series Dim graphSeriesBoxHeight 'height for boxes shown for colors of series 'you'd set this to the line thickness (or thereabouts) dim graphSeriesBoxXSpace 'space to leave for series box (X pixels) dim graphSeriesBoxYSpace 'space to leave for series box (Y pixels) dim graphImpliedGridX 'true to automatically draw in X grid lines --- dim graphImpliedGridY 'true to automatically draw in Y grid lines | dim graphGridColorExpr 'expression to choose colour for grid lines dim graphDefaultColorArray 'array of default colours for series dim graphPieLabel '="inside" to put in the slice, "outside" to put outside graphTop = 50 graphHeight = 400 graphWidth = 550 graphLeft = 50 graphSeriesBoxWidth = 30 graphSeriesBoxHeight = 30 graphSeriesBoxXSpace = 40 graphSeriesBoxYSpace = 40 graphImpliedGridX = true graphImpliedGridY = false graphGridColorExpr = "iif(value=0," & chr(34) & "red" & chr(34) & "," & chr(34) & "#E0E0E0" & chr(34) & ")" graphDefaultColorArray = Array("red","green","gold","blue","purple","orange" _ ,"lightblue","black","grey","brown" ,"yellow","pink") graphPieLabel = "outside" function DumpParameters(byval comment, byval parmArray) ' 'Description: Display the parameters about to be passed ' to a function, by writing them to the ' Response. 'Explanation: This is a diagnostic routine that I use ' during testing and debugging; it makes it ' easy to see what parameters I'm about to ' pass to a function call. 'Example: Say you have a call you consider suspect. ' ' f = x(a,b,c,d). ' ' You can dump the parameters using copy-paste, ' thus: ' ' DumpParameters "x", Array(a,b,c,d) ' dim elt Response.Write comment & "=(" for each elt in parmArray if isArray(elt) then Response.Write "Array(" & server.HTMLEncode(Join(elt, ",")) & ")" else Response.Write server.HTMLEncode(elt) end if Response.Write "," next Response.Write ")
" & vbCrlf end function '---------------------- 'BROWSER VERSION CHECKS '---------------------- 'Netscape 4.7 and earlier uses layers. 'Netscape 6 does not use layers, but is not quite the same 'as IE in some respects. For example, if you have ' in IE, it will 'display the table cell. In Netscape4.7 AND 6, you must 'put something *inside* the cell or it won't render at all. ' dim isNetscapeKnown dim isKnownToBeNetscape function isNetscapeLayerBrowser() if not isNetscapeKnown Then dim userAgent userAgent = Request.Servervariables("HTTP_USER_AGENT") isKnownToBeNetscape = (instr(1,userAgent,"Mozilla")=1 and instr(1,userAgent,"MSIE")=0) _ and (instr(1,userAgent,"Mozilla/5.0"))=0 'This last because NS6 is more like IE isNetscapeKnown = true end if isNetscapeLayerBrowser = isKnownToBeNetscape end function dim isNetscape6Known dim isKnownToBeNetscape6 function isNetscape6Browser() if not isNetscape6Known Then dim userAgent userAgent = Request.Servervariables("HTTP_USER_AGENT") isKnownToBeNetscape6 = (instr(1,userAgent,"Mozilla")=1 and instr(1,userAgent,"MSIE")=0) _ and 0<(instr(1,userAgent,"Mozilla/5.0")) 'This last because NS6 is more like IE isNetscape6Known = true end if isNetscape6Browser = isKnownToBeNetscape6 end function Function setVariant(byval newValue, byref variable) ' 'Description: Sets the value of a variant variable to a variant ' expression. 'Explanation: The Eval function gives you the ability to evaluate ' an arbitrary *expression*. It does not give you the ' ability to directly call a *procedure*. This sounds ' bad, but it isn't. By putting a series of statements ' to execute *into* a function, you can run them via Eval. ' That is, ' ' Eval("SetVariant(y,x)") ' ' is the closest we can get to: ' ' Eval("set x=y") ' ' and unlike Eval("x=y"), which merely indicates whether ' x and y are the same, it will *make* them the same ' if isobject(newvalue) Then set variable = newValue set setVariant = newValue else variable = newValue setVariant = newValue end if End Function Function iif(byval testThis, byval ifTrue, byval ifFalse) ' 'Description: Immediate If 'Parameters: testThis - Either true or false ' ifTrue - The value to return if testThis is true ' ifFalse - The value to return if testThis is false ' (or Null, or a non-boolean). 'Explanation: Equivalent to the IIF of Access and Excel ' if testThis Then iif = ifTrue else iif = ifFalse end if end function Function modifyColumns(byref columnIndexArray, byref formulaArray _ , byref scratch, byref dataArray) ' 'Description: Carry out operations on some or all of the columns ' of an array. 'Examples: (1) Convert column values to proportion of total (for column 1): ' ' sumofCol1 = modifyColumns(Array(1) _ ' , Array("setVariant(scratch+iff(isnull(value),value,0),scratch)*0+value") _ ' , 0, dataArray) ' modifyColumns Array(1),"value/" + cstr(sumOfCol1),dataArray ' ' (2) Rescale column values based on min->max range... ' To 0 through 1 (see rescaleColumnsOfArray): ' ' minmax = modifyColumns(Array(1) _ ' , Array("SetVariant(iif(value dataArray(i)(index) then if lbound(dataArray)" & vbCrlf low = lowArray(arrayIndex) high = highArray(arrayIndex) If IsEmpty(valueRanges(arrayIndex)) Then minMax = empty adjustMinMax columnIndex, dataArray, minmax else minmax = valueRanges(arrayIndex) end if if (minmax(1)<>minmax(0)) Then scaling = (high-low) / (minmax(1) - minmax(0)) else scaling = 1 end if if typename(dataArray(lbound(dataArray))(columnIndex)) = "String" Then 'Response.Write "ZX: Mapping strings in column " & Columnindex & "
" & vbCrlf mapStringColumnToLongs columnIndex, dataArray 'Response.Write "ZX: Strings mapped for column " & Columnindex & "
" & vbCrlf 'DumpArrayOfArraysAsTable dataArray,2,false 'ZX end if offset = (low - minmax(0)*scaling) 'difference between desired and actual minima if typename(minmax(0))="Date" then offset = cdbl(offset) formula = "cdbl(value)*" & cstr(scaling) & "+" & cstr(offset) else formula = "value*" & cstr(scaling) & "+" & cstr(offset) End If modifyColumns Array(columnIndex), Array(formula), empty , dataArray ValueRanges(arrayIndex) = minmax Next end sub sub cumulativeFrequencyForArray(byval countColumn _ , byval valueColumn, byval isTotalShown, byref dataarray) ' 'Description: ' mergeSortEntireArray dataArray, array(valueColumn) 'sort by the thing being sorted on if isTotalShown Then modifyColumns array(valueColumn), Array("setVariant(value+scratch,scratch)"), 0, dataArray End If modifyColumns array(countColumn), Array("setVariant(value+scratch,scratch)"), 0, dataArray end sub sub prepareArrayForFrequencyHistogram(byval countColumnIndex _ , byval valueColumnIndex, byval isTotalshown, byval barCount, byref dataArray) ' 'Description: Prepares for a graph where the population is the X axis 'Parameters: countColumnIndex - the index of the column which contains ' the count of the number of records (for each row) ' valueColumnIndex - the index of the column which contains ' the values to be plotted on the Y axis. ' isTotalShown - indicates if the accumulated *total* of ' the values in the [valueColumnIndex] is to be shown. ' If true, yes. If false, individual values are to be shown. ' barCount - number of bars to put in the histogram ' (e.g. for twenty bars, pass 20). ' dataArray - input/output : on entry, the data array to be ' prepared. On exit, a "condensed" version with barCount ' elements. ' if not isempty(dataArray) then cumulativeFrequencyForArray countColumnIndex, valueColumnIndex, isTotalShown, dataArray If ubound(dataArray) + 1 =scratch(0),scratch(0),value),scratch(0))*0" _ + "+SetVariant(iif(scratch(1)>=value,scratch(1),value),scratch(1))*0" _ + "+value") _ , Array(dataArray(0)(valuecolumnIndex),dataArray(0)(valuecolumnIndex)), dataArray) minValue = minmax(0) maxValue = minmax(1) ' 'Collapse the array, so that each output row will now have ' thisRow(valueColumnIndex) = upper threshold for the range ' thisRow(countColumnIndex) = sum of the counts for the rows where the value ' fell in the range indicated by the threshold ' (and the threshold of the previous output row) ' outputIndex = 0 threshold = minValue for inputIndex = 0 to ubound(dataArray) if inputIndex=0 Or threshold 2). ' It'd be good if there were a routine that could filter ' based *on an expression criterion*. But there isn't. ' Dim result Dim rowIndex Dim colIndex Dim isRowIncluded result = Empty If not isempty(filterMe) Then for rowIndex = lbound(filterMe) to ubound(filterMe) isRowIncluded = True For colIndex = lbound(filterColumnArray) to ubound(filterColumnArray) If CompareVariants(filterMe(rowIndex)(filterColumnArray(colIndex)) _ , filterValueArray(colIndex)) <> 0 Then isRowIncluded = False Exit For End If next If isRowIncluded Then AddElementToArray filterMe(rowIndex), result next End If filteredArray = result end function function min(a,b) if a in place. ' That's why you don't give (left,top) position and ' and positioning ("absolute" or "relative") parameter. ' To position the graphs drawn by this routine, use... ' ' plotPositionStart left,top,positioning ' plotCumulativeFrequencyGraph, v,width,height, ... ' plotPositionEnd ' 'Example: "score by population" subreport of ' "score distribution by round and attempt" report ' in cms/showreport.asp. ' 'Parameters: valueColIndex - index of column containing values ' pixelWidth - width of the graphing region ' pixelHeight - height of the graphing region ' barAttributeExpr - ' barCaptionExpr - ' dataArray - data points to plot ' Dim barCount Dim bar Dim thisRow if barAttributeExpr = "" Then barAttributeExpr = """bgcolor=red""" end if if barCaptionexpr = "" Then barCaptionExpr = "cstr(int((bar + 1)/barCount*10000+.5)/100) & ""%""" end if rescaleColumnsOfArray Array(valueColIndex), Array(0), Array(pixelHeight), dataArray If not isempty(dataArray) Then Response.Write "" & vbCrlf barCount = ubound(dataArray) + 1 For bar = 0 to ubound(dataArray) thisRow = dataArray(bar) Response.Write "
" Response.Write "" & vbCrlf if thisRow(valueColIndex)" 'Empty space above bar end if Response.Write "
" & vbCrlf Next Response.Write "
" & vbCrlf ' 'Captions at bottom ' if barCount<31 Then Dim stagger Dim modulo Response.Write "" & vbCrlf stagger = int((barCount+9) / 10) for modulo = 0 to stagger-1 if 0 " end if For bar = 0 to ubound(dataArray) if (stagger=1) or (bar mod stagger)=modulo then Response.Write " " & vbCrlf end if next Response.Write "" & vbCrlf next Response.Write "
" Response.Write eval(barCaptionExpr) Response.Write "
" & vbCrlf end if End If end function function plotXYScatterSeries(byval xIndex, byval yIndex _ , byref dataArray, byval pointExpr, byval offsetXexpr, byval offsetYexpr _ , byval positioning, byval layerExpr) ' 'Description: Plots a series in an X,Y scatter graph 'Note: Plots points, does *not* draw lines connecting the points. ' The x and y coordinates must *already* be scaled to the drawing ' region. 'Parameters: xIndex - The index of the column containing the X values ' yIndex - The index of the column containing the Y values ' dataArray - The data array ' pointExpr - An expression that will return the HTML to display ' for each data point (this can be used to set an ' with TITLE and HREF linking information, for example). ' offsetXExpr - Expression to use to calculate the offset between ' the top left corner of the HTML for the data point ' and the centre of the HTML for the data point (X). ' If "" is supplied, default is "5". ' offsetYExpr - As for offsetXExpr. ' positioning - "absolute" or "relative" - how to position the graph ' Dim rowIndex Dim x Dim y Dim thisRow Dim layerAttr if positioning="" then positioning = "absolute" if cstr(pointExpr) = "" Then pointExpr = """X""" end if If cstr(offsetXexpr) = "" then offsetXexpr = "5" If cstr(offsetYexpr) = "" then offsetYexpr = "5" If not isempty(dataArray) Then for rowIndex = lbound(dataArray) to ubound(dataArray) x = dataArray(rowIndex)(xIndex) - Eval(offsetXexpr) y = dataArray(rowIndex)(yIndex) - Eval(offsetYexpr) thisrow = dataArray(rowIndex) if (not isnull(x)) and (not isnull(y)) Then if ""100 then jumpX = jumpX * .9 if not floatingBar Then jumpY = getMinimumDifferenceForColumn(oldDataArray, yIndex) 'bar height 'problem. Our series might well all have the same y-value. 'We really need the minimum difference *before* the extraction 'of the current data if jumpY>100 then jumpY = jumpY * .9 end If For rowIndex = lbound(dataArray) To ubound(dataArray) dim onclick thisRow = dataArray(rowIndex) if isGannt Then x = thisRow(xIndex) x2 = thisRow(hIndex) if x2 tag for each block. ' layerExpr - for Netscape plotting, an expression ' to put in the tags. ' dim rowindex, x1, x2, y1, y2, thisRow if colorExpr="" then colorExpr = chr(34) & "red" & chr(34) if positioning="" then positioning = "absolute" If not isempty(dataArray) Then for rowIndex = lbound(dataArray) to ubound(dataArray) thisRow = dataArray(rowIndex) x1 = thisRow(x1Index) y1 = thisRow(y1Index) x2 = thisRow(x2Index) y2 = thisRow(y2Index) plotBlock Array(x1,y1), Array(x2,y2), positioning, Eval(colorExpr) _ , eval(attrExpr), "", layerAttr next end if end function function plotStackedArea(byval leftXTB, byval rightXTB _ , byval positioning, byval colorExpr, byval attrExpr _ , byval layerExpr, byref thisRow) ' 'Description: Plot a stacked area segment using as few TABLE ' elements as possible. 'Note: This is used for plotting stacked area graphs. ' See the XTBAreaSeries routine (which supplies it ' with the area segments to draw), and also the ' quickSeriesPlot() and stackDataArray() functions. ' dim x1, t1, b1, x2, t2, b2, temp dim xr,x,y,t,b Dim x0,t0,b0 dim layerAttr layerAttr="" if rightXTB(0)x2 then for xr = int(x1 + 1) to x2 + 1 step sgn(x2-x1) t = int((xr-x1)/(x2-x1) * (t2-t1) + t1 + .5) b = int((xr-x1)/(x2-x1) * (b2-b1) + b1 + .5) if t<> t0 or x2"String" Then lastX =lastX-1 else lastX ="X" & lastX if isEmpty(tIndex) then newCols = newCols+1: tIndex = ubound(firstRow)+newCols if isEmpty(bIndex) then newCols = newCols+1:bIndex = ubound(firstRow)+newCols if 0 < newCols Then AddEmptyColumnsToArray newCols, ubound(firstRow)+1, dataArray End If sumY = 0 for i = lbound(dataArray) to ubound(dataArray) if lastX<>dataArray(i)(xIndex) or isNull(seriesIndex) then sumY=0 dataArray(i)(bIndex) = sumY sumY = sumY + dataArray(i)(yIndex) dataArray(i)(tIndex) = sumY lastX=dataArray(i)(xIndex) next end if if not (isNull(seriesIndex)) then mergeSortEntireArray dataArray, array(seriesIndex) 're-sort by series end if end function function plotXYLineSeries(byval xIndex, byval yIndex _ , byref dataArray, byval colorExpr, byval thickness _ , byval positioning, byval layerExpr) ' 'Description: Plots a series in an X,Y line graph 'Note: The x and y coordinates must *already* be scaled to the drawing ' region. 'Parameters: xIndex - The index of the column containing the X values ' yIndex - The index of the column containing the Y values ' dataArray - The data array ' colorXExpr - Expression to use to calculate the colour. ' positioning - "absolute" or "relative" - how to position the graph ' Dim rowIndex 'index into the data array Dim x1, y1 'coordinates for the start of the next line segment Dim x2, y2 'coordinates for the finish of the next line segment Dim thisRow 'the current data row dim lastRow 'the previous data row dim layerAttr 'the evaluation of layerExpr for the current data row if colorExpr="" then colorExpr = chr(34) & "red" & chr(34) if positioning="" then positioning = "absolute" If not isempty(dataArray) Then for rowIndex = lbound(dataArray) to ubound(dataArray) x2 = dataArray(rowIndex)(xIndex) y2 = dataArray(rowIndex)(yIndex) thisrow = dataArray(rowIndex) if lbound(dataArray)" & vbCrlf end function function plotPositionEnd(positioning) if isNetscapeLayerBrowser() Then Response.Write "" else Response.Write "" end if Response.Write vbCrlf end function function plotVector(byval pointArray, byval positioning, byval color, byval thickness _ , byval layerAttr) if positioning="" then positioning = "absolute" dim i dim j for i = lbound(pointArray) to ubound(pointArray) - 1 j = i + 1 plotLine pointArray(i), pointArray(j), positioning, color, thickness, layerAttr next end function function offsetPictureAttr(byval x,byval y,byval pic) ' 'Description: Calculate the attributes to add to a tag ' (IE) or to a or
tag (NS4.7 and NS6) ' to display an offset picture slice. 'Parameters: x = current X coordinate ' y = current Y coordinate ' pic = URL of image to display (should not contain ' spaces) 'Explanation: To display a picture in an area built by rendering ' lots of narrow vertical tables (e.g. stacked area) ' or lots of narrow horizontal tables (e.g. pie), ' you need to separately offset the background picture ' in each of the tables. ' x= int(x+.5): y = int(y+.5) if isNetscapeLayerBrowser() then offsetPictureAttr = _ " style=" & chr(34) & "background-image: URL(" & pic & "); " _ & " background-repeat:repeat; " & chr(34) 'Todo: should add something like this: ' & " clip=""rect(" & chr(34) & cstr(y) & " 50 50 " & cstr(x) & """)" ' ... when I get the right for Netscape. 'Caveat. For netscape, space characters in the pic parameter stop this from 'working correctly. Please use references to URLs that do not contain spaces. else offsetPictureAttr = _ " style=" & chr(34) & "background-image: URL(" & pic & "); " _ & " background-repeat:repeat; background-position:" & cstr(-x) & "px " _ & cstr(-y) & "px;" & chr(34) end if end function function pictureAttr(byval pic) pictureAttr = "BACKGROUND=" & chr(34) & pic & chr(34) end function function plotBlock(byval leftTop, byval RightBottom, byval positioning, byval color _ , byval attr, byval content, byval layerAttr) ' 'Description: Plots a rectangular block using
, and " & vbcrlf, " " & vbCrlf _ , space(indentLevel) & " " & vbCrlf, EncodeForHTML Response.Write space(indentLevel) & "
. ' Also uses for NS4.7 and earlier, and
for NS6. 'Parameters: leftTop - Array(x1,y1) - the left and top coordinates ' rightBottom - Array(x2,y2) - the right and bottom coordinates ' positioning - either "absolute" or "relative" ' color - the background color to be used for the
. ' may be "". ' attr - Attributes for the tag ' content - HTML content to display *inside* the " Response.Write "" '& "" '--workaround needed for NS. Unless there is something to show in the table ' cell, Netscape does not render it at all. ' That's why we put in the reference to the transparent 1x1 GIF file. end if Response.Write ">" & content Response.Write "" Response.Write "
' layerAttr - layer attribute to pass to Netscape 4 (or 6!) 'Notes: This routine is used for plotting axes, bars, horizontal ' slices of pies, and vertical slices of stacked area charts ' You don't need to pass leftTop and rightBottom in the right ' order. ' Dim x 'actual left coordinate (usually leftTop(0)). Dim y 'actual top coordinate (usually leftTop(1)). Dim width 'actual width of the table cell Dim height 'actual height of the table cell width = abs(RightBottom(0) - leftTop(0)) + 1 height = abs(Rightbottom(1) - leftTop(1)) + 1 x = leftTop(0) y = leftTop(1) if rightBottom(0)
" & vbCrlf plotPositionEnd positioning end function sub swapVariants(byref a, byref b) dim temp if isobject(a) Then set temp = a else temp = a if isobject(b) then set a = b else a = b if isobject(temp) then set b = temp else b = temp end sub function plotLine(byval startPoint, byval endPoint, byval positioning, byval color _ , byval thickness, byval layerAttr) ' 'Description: Plots a line between two points 'Parameters: startPoint(0), startPoint(1) - x and y coordinates of start ' endPoint(0), endPoint(1) - x and y coordinates of end ' positioning - whether to use "absolute" or "relative" positioning ' color - the color to use ' thickness - how thick the line is to be drawn ' if isempty(thickness) then thickness = 1 dim leftbit dim rightbit leftbit = int(thickness/2) rightbit = thickness - leftbit if startPoint(0)=endPoint(0) or startPoint(1)=endPoint(1) then 'Is vertical or horizontal startPoint(0) = startPoint(0) - leftBit endPoint(0) = endPoint(0) + rightBit startPoint(1) = startPoint(1) - leftBit endPoint(1) = endPoint(1) + rightBit plotBlock startPoint, endPoint, positioning, color, "", "", layerAttr 'DumpParameters "plotBlock", Array(startPoint, endPoint, positioning, color, "", "", layerAttr) else dim x,y,jump,x2,y2 'While rendering if abs(startPoint(0)-endPoint(0)) > abs(startPoint(1)-endPoint(1)) Then 'X difference is greater, so step through Y's if endPoint(1) pixelsize/8/len(unitARray(tryUnit+1)) then tryUnit = tryUnit - 2 exit for end if next dateFormula = chr(34) & unitArray(tryUnit+1) & chr(34) if int(range(0))=0 and int(range(1))=0 Then 'Time only, no date component dateFormula = replace(dateFormula, "/", "") dateFormula = replace(dateFormula, "yyyy", "") dateFormula = replace(dateFormula, "mm", "") dateFormula = replace(dateFormula, "mmm", "") dateFormula = replace(dateFormula, "dd", "") end if dateFormula = replace(dateFormula, "yyyy", chr(34) & " & cstr(year(value))& " & chr(34)) dateFormula = replace(dateFormula, "mm", chr(34) & " & cstrn(month(value),2) & " & chr(34)) dateFormula = replace(dateFormula, "mmm", chr(34) & "& monthname(month(value)) & " & chr(34)) dateFormula = replace(dateFormula, "dd", chr(34) & " & cstrn(day(value),2)& " & chr(34)) dateFormula = replace(dateFormula, "hh", chr(34) & " & cstrn(hour(value),2)& " & chr(34)) dateFormula = replace(dateFormula, "nn", chr(34) & " & cstrn(minute(value),2)& " & chr(34)) dateFormula = replace(dateFormula, "ss", chr(34) & " & cstrn(second(value),2)& " & chr(34)) dateFormula = replace(dateFormula, "xx", chr(34) & " & cstr(secondfraction(value,100))& " & chr(34)) dateFormula = replace(dateFormula, "uu", chr(34) & " & cstr(secondfraction(value,1000000))& " & chr(34)) selectDateUnit = unitArray(tryUnit) end function function selectunit(byval range, byval pixelsize, byref displayFormula) ' 'Description: Given a range, and a width in pixels, decide how many ' values to plot (on an X or Y axis), and what "step" ' to use between values. 'Notes: The units selected are *not* appropriate for timestamp ' values. It'd be a good idea to build a version of this ' routine for calculating *timestamp* units. 'See Also: The axis plotting routines, which have *real* problems ' plotting date or timestamp information. ' dim powerOfTen ' Dim fractional ' Dim unit 'Fundamental unit to display along axis if typename(range(0)) = "Date" Then selectUnit = selectdateUnit(range, pixelsize, displayFormula) exit Function End If if range(1)=range(0) then selectUnit = 1 exit function end if powerofTen = log(cdbl(abs(range(1) - range(0)))) / log(10) - _ log(pixelSize/40) / log(10) fractional = powerOfTen - int(powerOfTen) powerOfTen = int(powerOfTen) if fractional< log(2)/log(10) Then unit = exp(poweroften*log(10)) elseif fractional< log(5)/log(10) then unit = 2 * exp(poweroften*log(10)) else unit = 5 * exp(poweroften*log(10)) end if selectunit = unit end function function plotXAxis(byval range, byval rect, byval color, byval axisName _ , byval minStep, byval stringValueArray) 'Description: plots an X axis for a graph 'Parameters: range(0) - lower value bound ' range(1) - upper value bound ' rect(0) - minimum X plotting coordinate ' rect(1) - minimum Y plotting coordinate ' rect(2) - maximum X plotting coordinate ' rect(3) - maximum Y plotting coordinate ' rect(4) - absolute/relative position 'Notes: Plotting of date/time information sucks. Sorry about that. ' It'd better be multiple days to be plotted, because all ' date/time values are formatted as "dd/mm". It's bad, I know. ' -JB ' if color="" then color=chr(34) & "black" & chr(34) if ubound(rect)=3 then AddelementToArray "absolute", rect if rect(4) = "" then rect(4) = "absolute" plotLine Array(rect(0),(rect(1)+rect(3))/2), Array(rect(2),(rect(1)+rect(3))/2) _ , rect(4), color, 1 , "" Dim unit dim value dim x Dim ybarlow dim ybarhigh dim displayFormula dim axis axis = "X" unit = selectUnit(range, int(abs(rect(2) - rect(0))), displayFormula) plotline Array(rect(0), rect(1)), Array(rect(0), rect(3)), rect(4), color, 1, "" 'Min 'DumpParameters "plotLine", Array(Array(rect(0), rect(1)), Array(rect(0), rect(3)), rect(4), color, 1, "") plotline Array(rect(2), rect(1)), Array(rect(2), rect(3)), rect(4), color, 1, "" 'Max ybarlow = rect(1) * 0.75 + rect(3) * 0.25 ybarhigh = rect(1) * 0.25 + rect(3) * 0.75 if minStep=1 then if unit < minStep then unit = minStep for value = int(range(0) / unit) * unit to range(1) step unit dim formattedValue if range(0)" & vbCrlf formattedValue = cstr(stringValueArray(value)) else formattedValue = cstr(value) end if else formattedValue = eval(displayFormula) end if x = (value - range(0)) / (range(1) - range(0)) * (rect(2) - rect(0)) + rect(0) if graphImpliedGridY Then plotLine Array(x,ybarhigh), Array(x,graphTop), rect(4), eval(graphGridColorExpr),1, "" end if plotLine Array(x,ybarlow), Array(x,ybarhigh), rect(4), color, 1, "" plotPositionStart x - len(formattedValue)*4, max(ybarlow,ybarhigh), rect(4), "" Response.Write Server.HTMLEncode(formattedValue) plotPositionEnd rect(4) end if next if """ & server.HTMLEncode(AxisName) & "" plotPositionEnd rect(4) end if end function function plotYAxis(byval range, byval rect, byval color, byval axisName, byval minStep _ , stringValueArray) ' 'As per plotXAxis. Captions are rendered to the *left* of the axis ' if ubound(rect)=3 then AddelementToArray "absolute", rect if rect(4) = "" then rect(4) = "absolute" plotLine Array((rect(0)+rect(2))/2,rect(1)), Array((rect(0)+rect(2))/2, rect(3)) _ , rect(4), color, 1 , "" Dim unit dim value dim y Dim xbarlow dim xbarhigh Dim displayFormula dim axis axis = "Y" unit = selectUnit(range, int(abs(rect(3) - rect(1))), displayFormula) if minStep=1 then if unit < minStep then unit = minStep plotline Array(rect(0), rect(1)), Array(rect(2), rect(1)), rect(4), color, 1, "" 'Min plotline Array(rect(0), rect(3)), Array(rect(2), rect(3)), rect(4), color, 1, "" 'Max xbarlow = rect(0) * 0.75 + rect(2) * 0.25 xbarhigh = rect(0) * 0.25 + rect(2) * 0.75 for value = int(range(0) / unit) * unit to range(1) step unit dim formattedValue if range(0)" & vbCrlf formattedValue = cstr(stringValueArray(value)) else formattedValue = cstr(value) end if else formattedValue = eval(displayFormula) end if y = (value - range(0)) / (range(1) - range(0)) * (rect(3) - rect(1)) + rect(1) if graphImpliedGridX Then plotLine Array(xbarlow,y), Array(graphWidth+graphLeft,y), rect(4), eval(graphGridColorExpr),1, "" end if plotLine Array(xbarlow,y), Array(xbarhigh,y), rect(4), color, 1, "" plotPositionStart xbarlow-len(cstr(formattedValue))*8, y-5, rect(4), "" Response.Write Server.HTMLEncode(formattedValue) plotPositionEnd rect(4) end if next if """ & server.HTMLEncode(AxisName) & "" plotPositionEnd rect(4) end if end function function differentValuesInColumn(byval thisArray, byval thisIndex) ' 'Description: Returns an array containing the distinct values found ' for the specified column of an array of arrays. 'Notes: As a side-effect, trashes the order of the array ' of arrays. 'Parameters: thisArray - array of arrays ' thisIndex - index to sort on ' dim result dim rowIndex if not isempty(thisArray) Then mergeSortEntireArray thisArray, array(thisIndex) AddElementToArray thisArray(lbound(ThisArray))(thisIndex), result for rowIndex = lbound(thisArray)+1 to ubound(thisArray) if CompareVariants(thisArray(rowIndex)(thisIndex) _ , thisARray(rowIndex-1)(thisIndex))<>0 Then AddElementToArray thisArray(rowIndex)(thisIndex), result End If next end if differentValuesInColumn = Result end function function plotPieSlice(byval cx, byval cy, byval w, byval h _ , byval radianStart, byval radianEnd, byval thisRow, byval attrexpr _ , byval colorExpr , byval positioning, byval layerExpr) dim tryY dim p 'Number of points around the circumference dim edgeX 'Current point on the edge of the circle dim edgeY 'Current point on the edge of the circle dim j dim lastedgeY dim pixelArray dim angle dim atLeft dim drawLeft dim i dim nextedgeY dim thisEdgeY 'Response.Write "XZ: Pie slice " & radianStart & " to " & radianEnd & "
" & vbCrlf lastEdgeY = -999 if radianStart int(edgeY+.5)then nextEdgeY = int(edgeY + .5) if lastEdgeY<>-999 then for thisEdgeY = lastEdgeY + sgn(nextEdgeY - lastEdgeY) to nextEdgeY step sgn(nextEdgeY - lastEdgeY) addElementToArray Array(int(edgeX+.5),thisEdgeY,iif(int(edgeX+.5)" & vbCrlf if abs(radianEnd - radianStart - 3.1415926 * 2)>1E-7 Then lastEdgeY = -999 dim maxsin dim thisSin, thisCos dim sameCosSign maxsin = max(sin(radianStart), sin(radianEnd)) sameCosSign = abs(sgn(cos(radianStart))+sgn(cos(radianEnd))) = 2 for each angle in Array(radianStart, radianEnd) thisSin = sin(angle) thisCos = cos(angle) if sameCosSign then atLeft= iif(thisSin" & vbCrlf 'Response.Write "QQQ: atLeft = " & atLeft & ", drawLeft = " & drawLeft & "
" & vbCrlf for j = 0 to w 'assumes h int(edgeY+.5) then lastedgeY = int(edgeY + .5) addElementToArray Array(int(edgeX+.5),lastEdgeY,atLeft+drawLeft), pixelArray 'column 2 is: ' 1 if at left and we are to draw to the left ' 2 if at left and we are to draw to the right ' 5 if at right and we are to draw to the left ' 6 if at right and we are to draw to the right end if next next end if mergeSortEntireArray pixelArray, array(1,2,0) 'sort by Y ' 'we now have all the dots along the edges of the shape 'we draw when we have adjacent rows in the array with 'matching column 1 (Y) and column 2 value pairs of: '(0,7), (0,1), (0,5), (2,7), (6,7), (2,5) 'We do NOT draw when the column 2 value pairs are: '(0,2), (0,6), (1,5), (1,6), (1,7), (2,6), (5,6), (5,7) 'Okay, I grant you, this is weird, but bear with us... ' ' 'Todo: special case: may have lots of 77 and 00 for horizontal 'lines drawn out from the centre! ' for i = 1 to ubound(pixelArray) if pixelArray(i-1)(1) = pixelArray(i)(1) then if 0 < instr(1, "07-01-05-27-67-25-", pixelArray(i-1)(2) & pixelArray(i)(2) & "-") then dim x,y dim attrValue dim layerAttr x= pixelArray(i-1)(0) y= pixelArray(i-1)(1) if """ & vbCrlf AddEmptyColumnsToArray 1, ubound(dataArray(0))+1, dataArray xIndexArray(0) = ubound(dataArray(0)) xIndex = xIndexArray(0) 'Response.Write "XZ: pie processing: dummy X column is " & xIndex & "
" & vbCrlf 'DumpArrayOfArraysAsTable dataArray,2,false 'XZ end if 'Response.Write "XZ: pie processing: stacking by X
" & vbCrlf stackDataArray xIndex, yIndex, dataArray, seriesIndex, tIndex, bIndex 'Response.Write "XZ: pie processing
: rescaling by X" & vbCrlf rescaleColumnsOfArray Array(tIndex), Array(0), Array(3.1415926*2), (dataArray), minMax if 0" & vbCrlf End Select if lcase(graphType)<>"pie" then prepareToGraph xIndexArray, yIndexArray, dataArray, XAxisTitle, YAxisTitle, positioning _ , false, false, isBarGraph , lcase(graphType)="gantt", 0.5, minMax end if if graphType="bar" then thinBar = thinBar / (minmax(0)(1) - minmax(0)(0)) * (graphWidth) 'Todo: drop hardcoding here end if Dim defaultColorArray defaultColorArray = graphDefaultColorArray if seriesColorExpr = "" Then 'If no colour rule supplied, choose based on series seriesColorExpr = "defaultColorArray(SeriesNumber mod (ubound(defaultColorArray)+1))" end If if lcase(graphType)="pie" then plotPieCharts xIndexArray, yIndexArray, expr, dataArray, positioning, seriesIndex _ , seriesTitleExpr, seriesColorExpr, layerExpr exit function end if for each seriesValue in seriesValueArray value = seriesValue seriesNumber = seriesNumber + 1 color = eval(seriesColorExpr) title = eval(seriesTitleExpr) filteredData = filteredArray((dataArray), Array(seriesIndex), Array(seriesValue)) Select case lcase(graphType) case "","scatter" plotXYScatterSeries xIndex, yIndex, filteredData, expr, offsetXexpr, offsetYexpr _ , positioning, layerExpr case "bar overlay", "bar" plotBarseries Array(xIndex), Array(yIndex), filteredData, expr, chr(34) & color & chr(34), graphHeight+graphTop, positioning _ , thinBar, layerExpr, dataArray case "line" plotXYLineSeries xIndex, yIndex, filteredData, chr(34) & color & chr(34), 2, positioning, layerExpr case "line+point" plotXYLineSeries xIndex, yIndex, filteredData, chr(34) & color & chr(34), 2, positioning, layerExpr plotXYScatterSeries xIndex, yIndex, dataArray, expr, offsetXexpr, offsetYexpr _ , positioning, layerExpr case "stacked area", "area" plotXTBAreaSeries xIndex, tIndex, bIndex, filteredData, chr(34) & color & chr(34) _ , positioning, expr, layerExpr case "stacked bar" plotBarSeries xIndexArray, Array(tIndex,bIndex), filteredData _ , expr, chr(34) & color & chr(34), graphHeight+graphTop, positioning, empty, layerExpr, dataArray case "gantt" plotBarSeries xIndexArray, yIndexArray, filteredData _ , expr, chr(34) & color & chr(34), graphHeight+graphTop, positioning, empty, layerExpr, dataArray case "pie" end select thisRow = filteredData(0) dim isPointGraph isPointgraph = instr(1,lcase(graphType),"point") writeSeriesLabel seriesNumber-1, color, title, positioning, graphLeft, graphHeight + graphTop + 30 _ , iif(isPointGraph,"",expr) , layerExpr, thisRow next end function function plotPieCharts(byval xIndexArray, byval yIndexArray, byval Attrexpr _ , byref dataArray, byval positioning, byval seriesIndex _ , byval seriesTitleExpr, byval seriesColorExpr, byval layerExpr) dim xValueArray dim x dim thisRow dim cx, cy, w, h dim pieCount dim piesAcross, pieX 'pies to draw in each row across the graph dim piesDown, pieY 'pies to dim xSubset 'subset of data array with matching X value dim seriesValueArray 'array of series found for current X value dim seriesValue 'next dim xvSubset 'subset of data array with matching X AND series values dim seriesNumber ' 'Response.Write "XZ: in plotPieCharts
" & vbCrlf 'Response.Write "XZ: seriesColorExpr is " & seriesColorExpr & "
" & vbCrlf if isNull(xIndex) then xValueArray=Array("") else 'Response.Write "XZ: getting values for col " & xIndex & "
" & vbCrlf xValueArray = differentValuesInColumn(dataArray, xIndex) end if pieCount = ubound(xValueArray) + 1 'Number of pies piesDown = int(sqr(pieCount)*graphHeight/graphWidth+.5) if piesDown = 0 then piesDown = 1 if pieCount < piesDown then piesDown = pieCount piesAcross = int((pieCount+piesDown-1)/piesDown) w = int(graphWidth/2/piesAcross) * .9 h = int(graphHeight/2/piesDown) * .9 if h" & vbCrlf plotPieSlice cx,cy,w,h,thisRow(yIndexArray(0)), thisRow(yIndexArray(1)) _ , thisRow, Attrexpr, chr(34) & eval(SeriescolorExpr) & chr(34), positioning, layerExpr if ""
" case "outside" plotPositionStart cx+sin(angle)*w - iif(sin(angle)<0,210,0) _ , cy+cos(angle)*w+.5 - iif(cos(angle)<0,12,0), positioning, "" Response.Write "
" end select Response.Write eval(seriesTitleExpr) Response.Write "
" & vbCrlf plotPositionEnd positioning end if end if seriesNumber = seriesNumber + 1 'Problem: what I have here is WRONG. ' Series number should actually be the index into ' differentValuesInColumn(***dataArray***,seriesIndex), ' where the current value is found. next plotpositionStart cx-h, cy+w, positioning, "" Response.Write "
" & vbCrlf Response.Write "
" Response.Write Server.HTMLEncode(x) & vbCrlf Response.Write "
" & vbCrlf plotPositionEnd positioning pieX = pieX + 1 if pieX = piesAcross then pieX=0: pieY = pieY + 1 next end function function prepareToGraph(byval xIndexArray, byval yIndexArray, byref dataArray _ , Byval XAxisTitle, byval YAxisTitle, byval positioning _ , byval ZeroXOrigin, byval ZeroYOrigin, byval isXBar, byval isYBar , byval barOffset _ , byref minMax) ' 'Description: Rescales an input array to fit a standard graphing region, ' and draws X and Y axes for it. ' dim barX dim barY dim xIndex dim yIndex xIndex = xIndexArray(0) yIndex = yIndexArray(0) barX = 0 barY = 0 if positioning="" then positioning = "absolute" rescaleColumnsOfArray Array(xIndex,yIndex), Array(graphLeft,graphHeight+graphTop), Array(graphWidth+graphLeft,graphTop), (dataArray) _ , minMax 'we pass data array by value to find the minima and maxima for the values in 'each index if 0 < ubound(xIndexArray) Then for each xIndex in xIndexArray: AdjustMinMax xIndex, dataArray, minMax(0): next end if xIndex = xIndexArray(0) if 0 < ubound(yIndexArray) Then for each yIndex in yIndexArray: AdjustMinMax yIndex, dataArray, minMax(1): next end if yIndex = yIndexArray(0) if isXBar then barX = getMinimumDifferenceForColumn ( dataArray, xIndex) minMax(0)(0) = minMax(0)(0) - (1-barOffset) * barX minMax(0)(1) = minMax(0)(1) + barOffset * barX end if if isYBar then barY = getMinimumDifferenceForColumn (dataArray, yIndex) minMax(1)(0) = minMax(1)(0) - (1-barOffset) * barY minMax(1)(1) = minMax(1)(1) + barOffset * barY end if if (zeroXOrigin or graphXOrigin) And 0 < minMax(0)(0) then minMax(0)(0) = 0 'todo: what if all are -ve ? if (zeroYOrigin or graphYOrigin) And 0 < minMax(1)(0) then minMax(1)(0) = 0 'todo: what if all are -ve ? dim stringValueArray if not isnull(XAxisTitle) Then stringvalueArray = Empty if Typename(dataArray(0)(xIndex))="String" Then stringValueArray = DifferentValuesInColumn(dataArray,xIndex) plotXAxis minMax(0), Array(graphLeft,graphTop+graphHeight-10,graphWidth+graphLeft,graphTop+graphHeight+10 _ ,positioning), """black""", XAxisTitle, barX, stringValueArray end if if not isnull(YAxisTitle) Then stringvalueArray = Empty if Typename(dataArray(0)(yIndex))="String" Then stringValueArray = DifferentValuesInColumn(dataArray,yIndex) 'Response.Write "ZX: yIndex = " & yIndex & "
" & vbCrlf plotYAxis minMax(1), Array(graphLeft-10,graphHeight+graphTop,graphLeft+10,graphTop,positioning) _ , "black", YAxisTitle, barY, stringValueArray end if for each xIndex in xIndexArray rescaleColumnsOfArray Array(xIndex), Array(graphLeft), Array(graphWidth+graphLeft), dataArray _ , Array(minMax(0)) next xIndex = xIndexArray(0) for each yIndex in yIndexArray rescaleColumnsOfArray Array(yIndex), Array(graphHeight+graphTop), Array(graphTop), dataArray _ , Array(minMax(1)) next yIndex = yIndexArray(0) end function function lineGraphSeries(byval dataArray _ , byval columnArray, byval columnValueArray _ , byval xIndex, byval yIndex, byval seriesName, byval colorExpr, byval positioning) filteredArray dataArray, columnArray, columnValueArray if not isempty(dataArray) Then plotXYLineSeries xIndex, yIndex, dataArray, colorExpr, 2, positioning End If end function function getMinimumDifferenceForColumn(dataArray, xAxisColumn) ' 'Get minimum distance between X values ' Dim xDistance Dim isdiffFound dim rowIndex isDiffFound = false if isempty(dataArray) then xDistance = Null Elseif typename(dataArray(lbound(dataArray))(xAxisColumn))="String" then xDistance = 1 else xDistance = 0 mergeSortEntireArray dataArray, Array(xAxisColumn) For rowIndex = lbound(dataArray) + 1 to ubound(dataArray) Dim diff diff = dataArray(rowIndex)(xAxiscolumn) - dataArray(rowIndex-1)(xAxisColumn) if 0 " & vbCrlf DumpArrayOfArrays TestResult, space(indentLevel) & "
", "
" & vbcrlf End Function Public function extractRecordsetDataToArrayOfArrays(byval thisRS, byref rowDataArray) Dim columnArray Redim columnArray(thisRS.Fields.Count-1) IF (0 < thisRS.RecordCount) Or (thisRS.RecordCount = -1) Then While (not thisRS.EOF) And Response.IsClientConnected For columnIndex = 0 to thisRS.Fields.Count-1 columnArray(columnIndex) = thisRS.Fields(columnIndex).Value Next addElementToArray columnArray, rowDataArray thisRS.MoveNext Wend End If end function ' 'Module: includeMergesort.asp ' Private Sub RedimensionArray(ByVal hibound _ , ByVal PreserveData, ByRef Target) ' 'Description: Routine for redimensioning an array. 'Explanation: This cannot always be done with a Redim ' statement. Consider, for example, ' redimensioning each array in an array ' of arrays. ' In your outer loop you might have ' outerArray - the array of arrays ' i - index into that array. ' But how do you change the dimension ' of outerArray(i)...? ' You can't use ' ' Redim Preserve outerArray(i)(newBound) ' ' as that is not syntactically valid. ' Hence this routine, which allows: ' ' RedimensionArray newBound,true _ ' outerArray(i) ' If PreserveData Then ReDim Preserve Target(hibound) Else ReDim Target(hibound) End If End Sub Private Function CreateIndexArray(ByVal lowbound _ , ByVal Highbound , ByRef Here) ' 'Description: Creates an array which contains ' all the indices in a specified ' range. 'Example: CreateIndexArray(0,4,x) ' is equivalent to ' x = Array(0,1,2,3,4). 'Explanation: The sorting routines in this module ' do not swap the actual array rows ' when they are determining the correct ' order for the sorted array. They ' swap indices INTO the array. ' Swapping the indices is faster for ' all but the smallest arrays. ' Dim Index RedimensionArray Highbound-lowBound, False, Here For Index = lowbound To Highbound Here(Index) = Index Next End Function Private Sub CopyElements(byval FirstIndex, byval LastIndex _ , byref SourceArray, byref TargetArray) ' 'Description: Copy elements between two arrays ' starting at FirstIndex and finishing ' at LastIndex. 'Notes: This routine exists only because ' there's a *much* faster way to execute ' long copies in VB, and these routines ' were copied out of a VB component. ' Dim Index For Index=FirstIndex To LastIndex TargetArray(Index) = SourceArray(Index) Next End Sub Private Function Comparerows(ByRef SortMe _ , ByVal row1, ByVal row2 , ByRef SortKey ) ' 'Description: Compares two rows of the same array of ' arrays (their indices supplied by row1 ' and row2), according to a sort key ' (which may include any number of columns). 'Example: If... ' ' a = array(array("cat",4),array("dog",4)) ' ' and you call ' ' CompareRows(0,1,a,Array(1,0)) ' ' it will return -1, because the values in ' rows 0 and 1 for column 1 match, but ' the value in row 0 for column 0 is < ' the value in row 1. 'Notes: Null comparison semantics are honoured. ' This has a serious performance cost, ' unfortunately. However, without it, ' a Mergesort may give the wrong answer. ' Dim Diff 'The sign of the difference of the most 'recent column comparison (-1,0 or 1) Dim Index 'The index into the sort key Dim field1 'The value from row1 for the current column Dim field2 'The value from row2 for the current column Dim sortIndex 'The index of the next column to compare Dim desc 'Will be 1 if ascending, -1 if descending Diff = 0 Index = LBound(SortKey) While Index <= UBound(SortKey) And Diff = 0 sortIndex = SortKey(Index) If 0 and styles ' with an inline stylesheet. 'Notes: Because the bar and line graphing routines use unclassed ' table cells, the standard "report numbers" stylesheet ' doesn't work correctly when graphing. Use ' writeStylelessReportHeaderToResponse instead for graphs. ' %> <%=Server.HTMLEncode(Title)%> <%end sub sub writeReportFooterToResponse()%> <%end sub%>