extensions/net.sf.basedb.reggie/trunk/resources/reports/boxplot.js

Code
Comments
Other
Rev Date Author Line
1820 06 Feb 13 olle 1   /**
1820 06 Feb 13 olle 2    *  Draws box plot in a canvas context of percentile data
1820 06 Feb 13 olle 3    *  supplied as a JSON object.
1820 06 Feb 13 olle 4    */
1880 22 Feb 13 nicklas 5   function createBoxPlot(boxPlotJsonObject, canvas, draw_area_wdt, draw_area_hgt, draw_scale_factor)
1820 06 Feb 13 olle 6   {
1820 06 Feb 13 olle 7     var ctx = canvas.getContext('2d');
1880 22 Feb 13 nicklas 8     
1880 22 Feb 13 nicklas 9     draw_area_wdt *= draw_scale_factor;
1880 22 Feb 13 nicklas 10     draw_area_hgt *= draw_scale_factor;
1880 22 Feb 13 nicklas 11     
1820 06 Feb 13 olle 12     ctx.clearRect(0, 0, draw_area_wdt, draw_area_hgt);
1820 06 Feb 13 olle 13     // Reserve area for scale and titles
1880 22 Feb 13 nicklas 14     var headerTitleHgt = 25 * draw_scale_factor;
1880 22 Feb 13 nicklas 15     var drect_xoff = 60 * draw_scale_factor;
1880 22 Feb 13 nicklas 16     var drect_yoff = 70 * draw_scale_factor + headerTitleHgt;
1880 22 Feb 13 nicklas 17     var header_text_top_off = 5 * draw_scale_factor;
1880 22 Feb 13 nicklas 18     var text_axis_off = 40 * draw_scale_factor;
1820 06 Feb 13 olle 19     // Calculate remaining plot area
1820 06 Feb 13 olle 20     var drect_w = draw_area_wdt - 2*drect_xoff;
1820 06 Feb 13 olle 21     var drect_h = draw_area_hgt - 2*drect_yoff;
1880 22 Feb 13 nicklas 22     
1880 22 Feb 13 nicklas 23     // Font sizes
1880 22 Feb 13 nicklas 24     var base_font_size = 11 * draw_scale_factor; // 11px scaled
1880 22 Feb 13 nicklas 25     var header1_font_size = base_font_size + 5 * draw_scale_factor; // 16px scaled
1880 22 Feb 13 nicklas 26     var header2_font_size = base_font_size + 4 * draw_scale_factor; // 15px scaled
1880 22 Feb 13 nicklas 27     var top_right_font_size = base_font_size + 1 * draw_scale_factor; // 12px scaled
1880 22 Feb 13 nicklas 28     var top_left_font_size = base_font_size; // 11px scaled
1880 22 Feb 13 nicklas 29     var y_title_font_size = header2_font_size; // 15px scaled
1880 22 Feb 13 nicklas 30     var y_scale_font_size = base_font_size; // 11px scaled
1880 22 Feb 13 nicklas 31     var x_scale_font_size = base_font_size; // 11px scaled
1880 22 Feb 13 nicklas 32     
1820 06 Feb 13 olle 33     // Draw rectangle enclosing plot area
1820 06 Feb 13 olle 34     ctx.strokeStyle = "#000000";
1880 22 Feb 13 nicklas 35     ctx.strokeRect(fixCoordinate(ctx, drect_xoff), fixCoordinate(ctx, drect_yoff), Math.floor(drect_w), Math.floor(drect_h));
1820 06 Feb 13 olle 36     // Add top header title 
2608 28 Aug 14 nicklas 37     var headerTitleTop = boxPlotJsonObject.headerTitleTop;
1880 22 Feb 13 nicklas 38     drawHeaderTitleTopCenter(headerTitleTop, header1_font_size, header_text_top_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 39     // Add top plot title 
2608 28 Aug 14 nicklas 40     var titleTop = boxPlotJsonObject.titleTop;
1880 22 Feb 13 nicklas 41     drawTitleTopCenter(titleTop, header2_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 42     // Add bottom plot title 
2608 28 Aug 14 nicklas 43     var titleBottom = boxPlotJsonObject.titleBottom;
1880 22 Feb 13 nicklas 44     drawTitleBottomCenter(titleBottom, header2_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 45     // Add plot top right sub-title
2608 28 Aug 14 nicklas 46     var subTitleRight = boxPlotJsonObject.subTitleRight;
1880 22 Feb 13 nicklas 47     drawSubTitleTopRight(subTitleRight, top_right_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 48     // Add plot top left sub-titles
2608 28 Aug 14 nicklas 49     var subTitleLeft01 = boxPlotJsonObject.subTitleLeft01;
2608 28 Aug 14 nicklas 50     var subTitleLeft02 = boxPlotJsonObject.subTitleLeft02;
2608 28 Aug 14 nicklas 51     var subTitleLeft03 = boxPlotJsonObject.subTitleLeft03;
1880 22 Feb 13 nicklas 52     drawSubTitleTopLeft(subTitleLeft01, 0, top_left_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1880 22 Feb 13 nicklas 53     drawSubTitleTopLeft(subTitleLeft02, 1, top_left_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1880 22 Feb 13 nicklas 54     drawSubTitleTopLeft(subTitleLeft03, 2, top_left_font_size, text_axis_off, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 55     // Add y-axis left title
2608 28 Aug 14 nicklas 56     var yAxisTitleLeft = boxPlotJsonObject.yAxisTitleLeft;
1880 22 Feb 13 nicklas 57     drawTitleYLeft(yAxisTitleLeft, y_title_font_size, ctx, drect_xoff-35*draw_scale_factor, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 58     // Add y-axis right title
2608 28 Aug 14 nicklas 59     var yAxisTitleRight = boxPlotJsonObject.yAxisTitleRight;
1880 22 Feb 13 nicklas 60     drawTitleYRight(yAxisTitleRight, y_title_font_size, ctx, drect_xoff+35*draw_scale_factor, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 61     // Calculate y-axis data value limits
1820 06 Feb 13 olle 62     var minDataValue = calculateMinDataValue(boxPlotJsonObject);
1820 06 Feb 13 olle 63     var maxDataValue = calculateMaxDataValue(boxPlotJsonObject);
1820 06 Feb 13 olle 64     // Calculate scale marker array
1820 06 Feb 13 olle 65     var numTicks = 7;
1820 06 Feb 13 olle 66     var scaleMarkerArr = calculateScaleMarkerArray(minDataValue, maxDataValue, numTicks);
1820 06 Feb 13 olle 67     // Add 2% buffer below min value and 5% buffer above max value
1820 06 Feb 13 olle 68     var dataValueRange = maxDataValue - minDataValue;
1820 06 Feb 13 olle 69     var min_y = minDataValue - 0.02*dataValueRange;
1820 06 Feb 13 olle 70     var max_y = maxDataValue + 0.05*dataValueRange;
1820 06 Feb 13 olle 71     // Trim scale marker array to only include values inside range
1820 06 Feb 13 olle 72     var trimmedScaleMarkerArr = trimScaleMarkerArray(scaleMarkerArr, min_y, max_y);
1820 06 Feb 13 olle 73     // Add small spacing buffer in pixels below and above value area
1880 22 Feb 13 nicklas 74     var min_yoff = 5 * draw_scale_factor;
1880 22 Feb 13 nicklas 75     var max_yoff = 5 * draw_scale_factor;
1854 18 Feb 13 olle 76     // Select number of decimals for scale marker text
1856 19 Feb 13 olle 77     // Select number of decimals to get one decimal digit different from 0, up to 3 decimals
1854 18 Feb 13 olle 78     var numberOfDecimals = 0;
1854 18 Feb 13 olle 79     for (i=0; i < trimmedScaleMarkerArr.length; i++)
1854 18 Feb 13 olle 80     {
1856 19 Feb 13 olle 81       var scaleValueTmp = trimmedScaleMarkerArr[i];
1856 19 Feb 13 olle 82       // Round scale value to 3 decimals (to make e.g. 2.99999993 -> 3.000)
1856 19 Feb 13 olle 83       scaleValueTmp = scaleValueTmp.toFixed(3);
1856 19 Feb 13 olle 84       var decimalPart = scaleValueTmp - Math.floor(scaleValueTmp);
1854 18 Feb 13 olle 85       if (decimalPart >= 0.05)
1854 18 Feb 13 olle 86       {
1854 18 Feb 13 olle 87         if (numberOfDecimals == 0 || numberOfDecimals > 1)
1854 18 Feb 13 olle 88         {
1854 18 Feb 13 olle 89           numberOfDecimals = 1;
1854 18 Feb 13 olle 90         }
1854 18 Feb 13 olle 91       }
1854 18 Feb 13 olle 92       else if (decimalPart >= 0.005)
1854 18 Feb 13 olle 93       {
1854 18 Feb 13 olle 94         if (numberOfDecimals == 0 || numberOfDecimals > 2)
1854 18 Feb 13 olle 95         {
1854 18 Feb 13 olle 96           numberOfDecimals = 2;
1854 18 Feb 13 olle 97         }
1854 18 Feb 13 olle 98       }
1854 18 Feb 13 olle 99       else if (decimalPart >= 0.0005)
1854 18 Feb 13 olle 100       {
1854 18 Feb 13 olle 101         if (numberOfDecimals == 0 || numberOfDecimals > 3)
1854 18 Feb 13 olle 102         {
1854 18 Feb 13 olle 103           numberOfDecimals = 3;
1854 18 Feb 13 olle 104         }
1854 18 Feb 13 olle 105       }
1854 18 Feb 13 olle 106     }
1856 19 Feb 13 olle 107     // Draw y-axis left scale markers with values
1856 19 Feb 13 olle 108     var scaleValue;
1845 15 Feb 13 olle 109     var markerText;
1820 06 Feb 13 olle 110     for (i=0; i < trimmedScaleMarkerArr.length; i++)
1820 06 Feb 13 olle 111     {
1820 06 Feb 13 olle 112       scaleValue = trimmedScaleMarkerArr[i];
1854 18 Feb 13 olle 113       markerText = scaleValue.toFixed(numberOfDecimals);
1880 22 Feb 13 nicklas 114       drawScaleMarkerYLeft(scaleValue, y_scale_font_size, markerText, min_y, max_y, min_yoff, max_yoff, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 115     }
1820 06 Feb 13 olle 116     // Find number of boxes in box plot
1820 06 Feb 13 olle 117     var boxNumTot = 0;
1820 06 Feb 13 olle 118     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 119     {
1820 06 Feb 13 olle 120       boxNumTot++;
1820 06 Feb 13 olle 121     }
1820 06 Feb 13 olle 122     // Draw box plot boxes
1820 06 Feb 13 olle 123     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 124     {
1820 06 Feb 13 olle 125       var boxNum = i;
1820 06 Feb 13 olle 126       var name = boxPlotJsonObject.percentileData[i].name;
1820 06 Feb 13 olle 127       var numItems = boxPlotJsonObject.percentileData[i].numItems;
1820 06 Feb 13 olle 128       var v1 = boxPlotJsonObject.percentileData[i].v1;
1820 06 Feb 13 olle 129       var v2 = boxPlotJsonObject.percentileData[i].v2;
1820 06 Feb 13 olle 130       var v3 = boxPlotJsonObject.percentileData[i].v3;
1820 06 Feb 13 olle 131       var v4 = boxPlotJsonObject.percentileData[i].v4;
1820 06 Feb 13 olle 132       var v5 = boxPlotJsonObject.percentileData[i].v5;
1820 06 Feb 13 olle 133       // Draw box plot box
1880 22 Feb 13 nicklas 134       var x_pad = 10 * draw_scale_factor;
1880 22 Feb 13 nicklas 135       drawBoxPlotBoxY(boxNum, boxNumTot, v1, v2, v3, v4, v5, min_y, max_y, min_yoff, max_yoff, x_pad, ctx, drect_xoff, drect_yoff, drect_w, drect_h, draw_scale_factor);
1880 22 Feb 13 nicklas 136       drawBoxPlotBoxScaleMarkerXBottom(boxNum, boxNumTot, name, x_scale_font_size, x_pad, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1880 22 Feb 13 nicklas 137       drawBoxPlotBoxScaleMarkerXTop(boxNum, boxNumTot, numItems, x_scale_font_size, x_pad, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
1820 06 Feb 13 olle 138     }
1820 06 Feb 13 olle 139     // Draw y-axis right scale markers with values and horizontal dotted guide lines
1820 06 Feb 13 olle 140     for (var i in boxPlotJsonObject.valueGuideLinesY)
1820 06 Feb 13 olle 141     {
2008 19 Jun 13 olle 142       if (boxPlotJsonObject.valueGuideLinesY[i] != null)
2008 19 Jun 13 olle 143       {
2008 19 Jun 13 olle 144         var value = boxPlotJsonObject.valueGuideLinesY[i].value;
2008 19 Jun 13 olle 145         var valueText = boxPlotJsonObject.valueGuideLinesY[i].text;
2008 19 Jun 13 olle 146         // Draw y-axis right scale markers with values
2008 19 Jun 13 olle 147         drawScaleMarkerYRight(value, valueText, y_scale_font_size, min_y, max_y, min_yoff, max_yoff, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
2008 19 Jun 13 olle 148         // Draw dotted horizontal lines at y axis right scale marker values
2008 19 Jun 13 olle 149         drawDottedLineY(value, min_y, max_y, min_yoff, max_yoff, ctx, drect_xoff, drect_yoff, drect_w, drect_h);
2008 19 Jun 13 olle 150       }
1820 06 Feb 13 olle 151     }
1820 06 Feb 13 olle 152   }
1820 06 Feb 13 olle 153    
1820 06 Feb 13 olle 154   /**
1820 06 Feb 13 olle 155    *  Calculate min data value for percentiles in box plot JSON object.
1820 06 Feb 13 olle 156    */
1820 06 Feb 13 olle 157   function calculateMinDataValue(boxPlotJsonObject)
1820 06 Feb 13 olle 158   {
1820 06 Feb 13 olle 159     // Find number of boxes in box plot
1820 06 Feb 13 olle 160     var boxNumTot = 0;
1820 06 Feb 13 olle 161     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 162     {
1820 06 Feb 13 olle 163       boxNumTot++;
1820 06 Feb 13 olle 164     }
1820 06 Feb 13 olle 165     // Calculate min percentile value
1820 06 Feb 13 olle 166     var minValue = 1000000000;
1820 06 Feb 13 olle 167     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 168     {
1820 06 Feb 13 olle 169       var boxNum = i;
1820 06 Feb 13 olle 170       var v1 = boxPlotJsonObject.percentileData[i].v1;
1820 06 Feb 13 olle 171       var v5 = boxPlotJsonObject.percentileData[i].v5;
1820 06 Feb 13 olle 172       if (v1 < minValue)
1820 06 Feb 13 olle 173       {
1820 06 Feb 13 olle 174         minValue = v1;
1820 06 Feb 13 olle 175       }
1820 06 Feb 13 olle 176       if (v5 < minValue)
1820 06 Feb 13 olle 177       {
1820 06 Feb 13 olle 178         minValue = v5;
1820 06 Feb 13 olle 179       }
1820 06 Feb 13 olle 180     }
1820 06 Feb 13 olle 181     return minValue;
1820 06 Feb 13 olle 182   }
1820 06 Feb 13 olle 183    
1820 06 Feb 13 olle 184   /**
1820 06 Feb 13 olle 185    *  Calculate max data value for percentiles in box plot JSON object.
1820 06 Feb 13 olle 186    */
1820 06 Feb 13 olle 187   function calculateMaxDataValue(boxPlotJsonObject)
1820 06 Feb 13 olle 188   {
1820 06 Feb 13 olle 189     // Find number of boxes in box plot
1820 06 Feb 13 olle 190     var boxNumTot = 0;
1820 06 Feb 13 olle 191     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 192     {
1820 06 Feb 13 olle 193       boxNumTot++;
1820 06 Feb 13 olle 194     }
1820 06 Feb 13 olle 195     // Calculate max percentile value
1820 06 Feb 13 olle 196     var maxValue = -1;
1820 06 Feb 13 olle 197     for (var i in boxPlotJsonObject.percentileData)
1820 06 Feb 13 olle 198     {
1820 06 Feb 13 olle 199       var boxNum = i;
1820 06 Feb 13 olle 200       var v1 = boxPlotJsonObject.percentileData[i].v1;
1820 06 Feb 13 olle 201       var v5 = boxPlotJsonObject.percentileData[i].v5;
1820 06 Feb 13 olle 202       if (v1 > maxValue)
1820 06 Feb 13 olle 203       {
1820 06 Feb 13 olle 204         maxValue = v1;
1820 06 Feb 13 olle 205       }
1820 06 Feb 13 olle 206       if (v5 > maxValue)
1820 06 Feb 13 olle 207       {
1820 06 Feb 13 olle 208         maxValue = v5;
1820 06 Feb 13 olle 209       }
1820 06 Feb 13 olle 210     }
1820 06 Feb 13 olle 211     return maxValue;
1820 06 Feb 13 olle 212   }
1820 06 Feb 13 olle 213
1820 06 Feb 13 olle 214   /**
1820 06 Feb 13 olle 215    *  Draws centered top header title in a canvas context, using coordinates
1820 06 Feb 13 olle 216    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 217    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 218    */
1880 22 Feb 13 nicklas 219   function drawHeaderTitleTopCenter(text, font_size, text_top_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 220   {
1820 06 Feb 13 olle 221     // Add centered top header title
1880 22 Feb 13 nicklas 222     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 223     context.textBaseline = 'top';
1820 06 Feb 13 olle 224     context.textAlign="center";
1820 06 Feb 13 olle 225     context.fillText(text, xoff + wdt/2, text_top_off);
1820 06 Feb 13 olle 226   }
1820 06 Feb 13 olle 227    
1820 06 Feb 13 olle 228   /**
1820 06 Feb 13 olle 229    *  Draws centered top title in a canvas context, using coordinates
1820 06 Feb 13 olle 230    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 231    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 232    */
1880 22 Feb 13 nicklas 233   function drawTitleTopCenter(text, font_size, text_axis_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 234   {
1820 06 Feb 13 olle 235     // Add centered top title
1820 06 Feb 13 olle 236     var text_axis_yoff = -text_axis_off;
1880 22 Feb 13 nicklas 237     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 238     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 239     context.textAlign="center";
1820 06 Feb 13 olle 240     context.fillText(text, xoff + wdt/2, yoff + text_axis_yoff);
1820 06 Feb 13 olle 241   }
1820 06 Feb 13 olle 242    
1820 06 Feb 13 olle 243   /**
1820 06 Feb 13 olle 244    *  Draws centered bottom title in a canvas context, using coordinates
1820 06 Feb 13 olle 245    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 246    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 247    */
1880 22 Feb 13 nicklas 248   function drawTitleBottomCenter(text, font_size, text_axis_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 249   {
1820 06 Feb 13 olle 250     // Add centered bottom title
1820 06 Feb 13 olle 251     var text_axis_yoff = text_axis_off;
1880 22 Feb 13 nicklas 252     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 253     context.textBaseline = 'top';
1820 06 Feb 13 olle 254     context.textAlign="center";
1820 06 Feb 13 olle 255     context.fillText(text, xoff + wdt/2, yoff + hgt + text_axis_yoff);
1820 06 Feb 13 olle 256   }
1820 06 Feb 13 olle 257    
1820 06 Feb 13 olle 258   /**
1820 06 Feb 13 olle 259    *  Draws right-aligned top sub-title in a canvas context, using coordinates
1820 06 Feb 13 olle 260    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 261    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 262    */
1880 22 Feb 13 nicklas 263   function drawSubTitleTopRight(text, font_size, text_axis_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 264   {
1820 06 Feb 13 olle 265     // Add right-aligned top title
1820 06 Feb 13 olle 266     var text_axis_yoff = -text_axis_off;
1880 22 Feb 13 nicklas 267     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 268     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 269     context.textAlign="right";
1820 06 Feb 13 olle 270     context.fillText(text, xoff + wdt, yoff + text_axis_yoff);
1820 06 Feb 13 olle 271   }
1820 06 Feb 13 olle 272    
1820 06 Feb 13 olle 273   /**
1820 06 Feb 13 olle 274    *  Draws left-aligned top sub-title in a canvas context, using coordinates
1820 06 Feb 13 olle 275    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 276    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 277    */
1880 22 Feb 13 nicklas 278   function drawSubTitleTopLeft(text, sub_title_num, font_size, text_axis_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 279   {
1820 06 Feb 13 olle 280     // Add left-aligned top title
1820 06 Feb 13 olle 281     var text_axis_yoff = -text_axis_off;
1820 06 Feb 13 olle 282     //var text_yoff = 35 - 11*(2 - sub_title_num);
1820 06 Feb 13 olle 283     var text_axis_yoff = -text_axis_off;
1880 22 Feb 13 nicklas 284     var text_yoff = yoff + text_axis_yoff - (font_size+1)*(2 - sub_title_num);
1880 22 Feb 13 nicklas 285     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 286     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 287     context.textAlign="left";
1820 06 Feb 13 olle 288     context.fillText(text, xoff, text_yoff);
1820 06 Feb 13 olle 289   }
1820 06 Feb 13 olle 290    
1820 06 Feb 13 olle 291   /**
1820 06 Feb 13 olle 292    *  Draws vertical box plot box with five percentiles.
1820 06 Feb 13 olle 293    *  Box numbers starts from 0 at the left.
1820 06 Feb 13 olle 294    */
1880 22 Feb 13 nicklas 295   function drawBoxPlotBoxY(boxNum, boxNumTot, v1, v2, v3, v4, v5, min, max, min_off, max_off, x_pad, context, xoff, yoff, wdt, hgt, draw_scale_factor)
1820 06 Feb 13 olle 296   {
1820 06 Feb 13 olle 297     // Calculate value offsets
1820 06 Feb 13 olle 298     var eff_hgt = hgt - min_off - max_off;
1820 06 Feb 13 olle 299     var value_range = max - min;
1820 06 Feb 13 olle 300     var v1_yoff = min_off + eff_hgt*((v1 - min)/value_range);
1820 06 Feb 13 olle 301     var v2_yoff = min_off + eff_hgt*((v2 - min)/value_range);
1820 06 Feb 13 olle 302     var v3_yoff = min_off + eff_hgt*((v3 - min)/value_range);
1820 06 Feb 13 olle 303     var v4_yoff = min_off + eff_hgt*((v4 - min)/value_range);
1820 06 Feb 13 olle 304     var v5_yoff = min_off + eff_hgt*((v5 - min)/value_range);
1820 06 Feb 13 olle 305     // Calculate box width
1820 06 Feb 13 olle 306     var width_per_box = (wdt - 2*x_pad)/boxNumTot;
1820 06 Feb 13 olle 307     // Reserve 20% as space between boxes
1820 06 Feb 13 olle 308     var box_pad = width_per_box/10;
1820 06 Feb 13 olle 309     var box_wdt = width_per_box*4/5;
1820 06 Feb 13 olle 310     // Calculate box min, center, and max offsets
1820 06 Feb 13 olle 311     var box_xmin = x_pad + boxNum*width_per_box + box_pad;
1820 06 Feb 13 olle 312     var box_xcenter = box_xmin + box_wdt/2;
1820 06 Feb 13 olle 313     var box_xmax = box_xmin + box_wdt;
1820 06 Feb 13 olle 314     // Draw box rectangle
1820 06 Feb 13 olle 315     drawRect(box_xmin, v2_yoff, box_wdt, v4_yoff - v2_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 316     // Draw median line (thick)
1880 22 Feb 13 nicklas 317     context.lineWidth = 3;
1820 06 Feb 13 olle 318     context.beginPath();
1820 06 Feb 13 olle 319     drawLine(box_xmin, v3_yoff, box_xmax, v3_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 320     context.stroke();
1820 06 Feb 13 olle 321     // Draw line between first percentile and box (dashed line)
1820 06 Feb 13 olle 322     context.lineWidth = 1;
1820 06 Feb 13 olle 323     context.beginPath();
1820 06 Feb 13 olle 324     //drawLine(box_xcenter, v1_yoff, box_xcenter, v2_yoff, context, xoff, yoff, wdt, hgt);
1880 22 Feb 13 nicklas 325     var dash_size = 5 * draw_scale_factor;
1880 22 Feb 13 nicklas 326     drawDashedLineVertical(box_xcenter, v1_yoff, v2_yoff, context, xoff, yoff, wdt, hgt, dash_size, dash_size);
1820 06 Feb 13 olle 327     // Draw short horizontal line at end of line
1820 06 Feb 13 olle 328     drawLine(box_xcenter - box_wdt/4, v1_yoff, box_xcenter + box_wdt/4, v1_yoff, context, xoff, yoff, wdt, hgt);    
1820 06 Feb 13 olle 329     context.stroke();
1820 06 Feb 13 olle 330     // Draw line between box and fifth percentile (dashed line)
1820 06 Feb 13 olle 331     context.lineWidth = 1;
1820 06 Feb 13 olle 332     context.beginPath();
1820 06 Feb 13 olle 333     //drawLine(box_xcenter, v4_yoff, box_xcenter, v5_yoff, context, xoff, yoff, wdt, hgt);
1880 22 Feb 13 nicklas 334     drawDashedLineVertical(box_xcenter, v4_yoff, v5_yoff, context, xoff, yoff, wdt, hgt, dash_size, dash_size);
1820 06 Feb 13 olle 335     // Draw short horizontal line at end of line
1820 06 Feb 13 olle 336     drawLine(box_xcenter - box_wdt/4, v5_yoff, box_xcenter + box_wdt/4, v5_yoff, context, xoff, yoff, wdt, hgt);    
1820 06 Feb 13 olle 337     context.stroke();
1820 06 Feb 13 olle 338   }
1820 06 Feb 13 olle 339    
1820 06 Feb 13 olle 340   /**
1820 06 Feb 13 olle 341    *  Draws x-axis bottom scale marker with text in a canvas context
1820 06 Feb 13 olle 342    *  for a box plot box, using coordinates where x increases to the right
1820 06 Feb 13 olle 343    *  and y increases upwards, and where the offsets are added automatically.
1820 06 Feb 13 olle 344    */
1880 22 Feb 13 nicklas 345   function drawBoxPlotBoxScaleMarkerXBottom(boxNum, boxNumTot, value, font_size, x_pad, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 346   {
1820 06 Feb 13 olle 347     // Calculate box width
1820 06 Feb 13 olle 348     var width_per_box = (wdt - 2*x_pad)/boxNumTot;
1820 06 Feb 13 olle 349     // Reserve 20% as space between boxes
1820 06 Feb 13 olle 350     var box_pad = width_per_box/10;
1820 06 Feb 13 olle 351     var box_wdt = width_per_box*4/5;
1820 06 Feb 13 olle 352     // Calculate box min, center, and max offsets
1820 06 Feb 13 olle 353     var box_xmin = x_pad + boxNum*width_per_box + box_pad;
1820 06 Feb 13 olle 354     var box_xcenter = box_xmin + box_wdt/2;
1820 06 Feb 13 olle 355     var box_xmax = box_xmin + box_wdt;
1820 06 Feb 13 olle 356     // Draw scale marker
1880 22 Feb 13 nicklas 357     var marker_len = font_size;
1820 06 Feb 13 olle 358     context.lineWidth = 1;
1820 06 Feb 13 olle 359     context.beginPath();
1820 06 Feb 13 olle 360     drawLine(box_xcenter, 0, box_xcenter, -marker_len, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 361     context.stroke();
1820 06 Feb 13 olle 362     // Draw scale marker value
1820 06 Feb 13 olle 363     var value_text = '' + value;
1880 22 Feb 13 nicklas 364     var text_axis_yoff = 1.5*font_size;
1880 22 Feb 13 nicklas 365     var text_marker_xoff = font_size / 2;
1820 06 Feb 13 olle 366     context.save();
1880 22 Feb 13 nicklas 367     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 368     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 369     context.translate(xoff + box_xcenter + text_marker_xoff, yoff + hgt + text_axis_yoff);
1820 06 Feb 13 olle 370     context.rotate(-Math.PI/2);
1820 06 Feb 13 olle 371     context.textAlign = "right";
1820 06 Feb 13 olle 372     context.fillText(value_text, 0, 0);
1820 06 Feb 13 olle 373     context.restore();
1820 06 Feb 13 olle 374   }
1820 06 Feb 13 olle 375    
1820 06 Feb 13 olle 376   /**
1820 06 Feb 13 olle 377    *  Draws x-axis top scale marker with text in a canvas context
1820 06 Feb 13 olle 378    *  for a box plot box, using coordinates where x increases to the right
1820 06 Feb 13 olle 379    *  and y increases upwards, and where the offsets are added automatically.
1820 06 Feb 13 olle 380    */
1880 22 Feb 13 nicklas 381   function drawBoxPlotBoxScaleMarkerXTop(boxNum, boxNumTot, value, font_size, x_pad, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 382   {
1820 06 Feb 13 olle 383     // Calculate box width
1820 06 Feb 13 olle 384     var width_per_box = (wdt - 2*x_pad)/boxNumTot;
1820 06 Feb 13 olle 385     // Reserve 20% as space between boxes
1820 06 Feb 13 olle 386     var box_pad = width_per_box/10;
1820 06 Feb 13 olle 387     var box_wdt = width_per_box*4/5;
1820 06 Feb 13 olle 388     // Calculate box min, center, and max offsets
1820 06 Feb 13 olle 389     var box_xmin = x_pad + boxNum*width_per_box + box_pad;
1820 06 Feb 13 olle 390     var box_xcenter = box_xmin + box_wdt/2;
1820 06 Feb 13 olle 391     var box_xmax = box_xmin + box_wdt;
1820 06 Feb 13 olle 392     // Draw scale marker
1880 22 Feb 13 nicklas 393     var marker_len = font_size;
1820 06 Feb 13 olle 394     context.lineWidth = 1;
1820 06 Feb 13 olle 395     context.beginPath();
1820 06 Feb 13 olle 396     drawLine(box_xcenter, hgt, box_xcenter, hgt + marker_len, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 397     context.stroke();
1820 06 Feb 13 olle 398     // Draw scale marker value
1820 06 Feb 13 olle 399     var value_text = '' + value;
1880 22 Feb 13 nicklas 400     var text_axis_yoff = 1.5*font_size;
1880 22 Feb 13 nicklas 401     var text_marker_xoff = font_size / 2;
1820 06 Feb 13 olle 402     context.save();
1880 22 Feb 13 nicklas 403     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 404     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 405     context.translate(xoff + box_xcenter + text_marker_xoff, yoff - text_axis_yoff);
1820 06 Feb 13 olle 406     context.rotate(-Math.PI/2);
1820 06 Feb 13 olle 407     context.textAlign = "left";
1820 06 Feb 13 olle 408     context.fillText(value_text, 0, 0);
1820 06 Feb 13 olle 409     context.restore();
1820 06 Feb 13 olle 410   }
1820 06 Feb 13 olle 411    
1820 06 Feb 13 olle 412   /**
1820 06 Feb 13 olle 413    *  Draws y-axis left scale marker with text in a canvas context, using coordinates
1820 06 Feb 13 olle 414    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 415    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 416    */
1880 22 Feb 13 nicklas 417   function drawScaleMarkerYLeft(value, font_size, markerText, min, max, min_off, max_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 418   {
1820 06 Feb 13 olle 419     var eff_hgt = hgt - min_off - max_off;
1820 06 Feb 13 olle 420     var value_range = max - min;
1820 06 Feb 13 olle 421     var marker_yoff = min_off + eff_hgt*((value - min)/value_range);
1880 22 Feb 13 nicklas 422     var marker_len = font_size;
1820 06 Feb 13 olle 423     // Draw scale marker
1820 06 Feb 13 olle 424     context.lineWidth = 1;
1820 06 Feb 13 olle 425     context.beginPath();
1820 06 Feb 13 olle 426     drawLine(0, marker_yoff, -marker_len, marker_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 427     context.stroke();
1820 06 Feb 13 olle 428     // Draw scale marker value
1845 15 Feb 13 olle 429     var value_text = '' + markerText;
1880 22 Feb 13 nicklas 430     var text_axis_xoff = -1.5*font_size;
1880 22 Feb 13 nicklas 431     var text_marker_yoff = -font_size / 2;
1880 22 Feb 13 nicklas 432     context.font = font_size+'px sans-serif';
1820 06 Feb 13 olle 433     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 434     context.textAlign="right";
1820 06 Feb 13 olle 435     drawText(value_text, text_axis_xoff, marker_yoff + text_marker_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 436   }
1820 06 Feb 13 olle 437    
1820 06 Feb 13 olle 438   /**
1820 06 Feb 13 olle 439    *  Draws y-axis right scale marker with text in a canvas context, using coordinates
1820 06 Feb 13 olle 440    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 441    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 442    */
1880 22 Feb 13 nicklas 443   function drawScaleMarkerYRight(value, markerText, font_size, min, max, min_off, max_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 444   {
1820 06 Feb 13 olle 445     var eff_hgt = hgt - min_off - max_off;
1820 06 Feb 13 olle 446     var value_range = max - min;
1820 06 Feb 13 olle 447     var marker_yoff = min_off + eff_hgt*((value - min)/value_range);
1880 22 Feb 13 nicklas 448     var marker_len = font_size;
1820 06 Feb 13 olle 449     // Draw scale marker
1820 06 Feb 13 olle 450     context.lineWidth = 1;
1820 06 Feb 13 olle 451     context.beginPath();
1820 06 Feb 13 olle 452     drawLine(wdt, marker_yoff, wdt + marker_len, marker_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 453     context.stroke();
1820 06 Feb 13 olle 454     // Draw scale marker value
1845 15 Feb 13 olle 455     var value_text = '' + markerText;
1880 22 Feb 13 nicklas 456     var text_axis_xoff = font_size * 1.5;
1880 22 Feb 13 nicklas 457     var text_marker_yoff = -font_size / 2;
1880 22 Feb 13 nicklas 458     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 459     context.textBaseline = 'bottom';
1820 06 Feb 13 olle 460     context.textAlign="left";
1820 06 Feb 13 olle 461     drawText(value_text, wdt + text_axis_xoff, marker_yoff + text_marker_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 462   }
1820 06 Feb 13 olle 463    
1820 06 Feb 13 olle 464   /**
1820 06 Feb 13 olle 465    *  Draws dotted line for y-value in a canvas context, using coordinates
1820 06 Feb 13 olle 466    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 467    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 468    */
1820 06 Feb 13 olle 469   function drawDottedLineY(value, min, max, min_off, max_off, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 470   {
1820 06 Feb 13 olle 471     var eff_hgt = hgt - min_off - max_off;
1820 06 Feb 13 olle 472     var value_range = max - min;
1820 06 Feb 13 olle 473     var line_yoff = min_off + eff_hgt*((value - min)/value_range);
1820 06 Feb 13 olle 474     // Draw dotted line for y-value
1820 06 Feb 13 olle 475     context.lineWidth = 1;
1820 06 Feb 13 olle 476     context.beginPath();
1820 06 Feb 13 olle 477     drawDottedLineHorizontal(0, wdt, line_yoff, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 478     context.stroke();
1820 06 Feb 13 olle 479   }
1820 06 Feb 13 olle 480    
1820 06 Feb 13 olle 481   /**
1820 06 Feb 13 olle 482    *  Draws y-axis left scale title in a canvas context, using coordinates
1820 06 Feb 13 olle 483    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 484    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 485    */
1880 22 Feb 13 nicklas 486   function drawTitleYLeft(title, font_size, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 487   {
1820 06 Feb 13 olle 488     // Add y axis title
1820 06 Feb 13 olle 489     context.save();
1880 22 Feb 13 nicklas 490     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 491     context.textBaseline = 'bottom';
1880 22 Feb 13 nicklas 492     context.translate(xoff, yoff + hgt/2);
1820 06 Feb 13 olle 493     context.rotate(-Math.PI/2);
1820 06 Feb 13 olle 494     context.textAlign = "center";
1820 06 Feb 13 olle 495     context.fillText(title, 0, 0);
1820 06 Feb 13 olle 496     context.restore();
1820 06 Feb 13 olle 497   }
1820 06 Feb 13 olle 498    
1820 06 Feb 13 olle 499   /**
1820 06 Feb 13 olle 500    *  Draws y-axis right scale title in a canvas context, using coordinates
1820 06 Feb 13 olle 501    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 502    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 503    */
1880 22 Feb 13 nicklas 504   function drawTitleYRight(title, font_size, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 505   {
1820 06 Feb 13 olle 506     // Add y axis title
1820 06 Feb 13 olle 507     context.save();
1880 22 Feb 13 nicklas 508     context.font = font_size + 'px sans-serif';
1820 06 Feb 13 olle 509     context.textBaseline = 'top';
1880 22 Feb 13 nicklas 510     context.translate(xoff + wdt, yoff + hgt/2);
1820 06 Feb 13 olle 511     context.rotate(-Math.PI/2);
1820 06 Feb 13 olle 512     context.textAlign = "center";
1820 06 Feb 13 olle 513     context.fillText(title, 0, 0);
1820 06 Feb 13 olle 514     context.restore();
1820 06 Feb 13 olle 515   }
1820 06 Feb 13 olle 516    
1820 06 Feb 13 olle 517   /**
1820 06 Feb 13 olle 518    *  Draws a rectangle in a canvas context, using coordinates
1820 06 Feb 13 olle 519    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 520    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 521    *  Rectangle input is given as coordinates for lower left corner
1820 06 Feb 13 olle 522    *  and rectangle width and height. 
1820 06 Feb 13 olle 523    */
1820 06 Feb 13 olle 524   function drawRect(x, y, rect_wdt, rect_hgt, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 525   {
1880 22 Feb 13 nicklas 526     var x_with_offset = fixCoordinate(context, x + xoff);
1880 22 Feb 13 nicklas 527     var y_with_offset = fixCoordinate(context, yoff + hgt - y - rect_hgt);
1820 06 Feb 13 olle 528     // Draw box rectangle
1820 06 Feb 13 olle 529     context.strokeStyle = "#000000";
1880 22 Feb 13 nicklas 530     context.strokeRect(x_with_offset, y_with_offset, Math.floor(rect_wdt), Math.floor(rect_hgt));
1820 06 Feb 13 olle 531   }
1820 06 Feb 13 olle 532    
1820 06 Feb 13 olle 533   /**
1820 06 Feb 13 olle 534    *  Draws a line in a canvas context, using coordinates
1820 06 Feb 13 olle 535    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 536    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 537    */
1820 06 Feb 13 olle 538   function drawLine(x1, y1, x2, y2, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 539   {
1880 22 Feb 13 nicklas 540     var x1_with_offset = fixCoordinate(context, x1 + xoff);
1880 22 Feb 13 nicklas 541     var x2_with_offset = fixCoordinate(context, x2 + xoff);
1880 22 Feb 13 nicklas 542     var y1_with_offset = fixCoordinate(context, yoff + hgt - y1);
1880 22 Feb 13 nicklas 543     var y2_with_offset = fixCoordinate(context, yoff + hgt - y2);
1820 06 Feb 13 olle 544     context.moveTo(x1_with_offset, y1_with_offset);
1820 06 Feb 13 olle 545     context.lineTo(x2_with_offset, y2_with_offset);
1820 06 Feb 13 olle 546   }
1880 22 Feb 13 nicklas 547   
1820 06 Feb 13 olle 548   /**
1820 06 Feb 13 olle 549    *  Draws a dashed vertical line in a canvas context, using coordinates
1820 06 Feb 13 olle 550    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 551    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 552    */
1880 22 Feb 13 nicklas 553   function drawDashedLineVertical(x, y1, y2, context, xoff, yoff, wdt, hgt, dash_size, space_size)
1820 06 Feb 13 olle 554   {
1820 06 Feb 13 olle 555     var sign = 1;
1820 06 Feb 13 olle 556     if (y1 > y2)
1820 06 Feb 13 olle 557     {
1820 06 Feb 13 olle 558       sign = -1;
1820 06 Feb 13 olle 559     }
1820 06 Feb 13 olle 560     var y = y1;
1820 06 Feb 13 olle 561     var y_new;
1820 06 Feb 13 olle 562     while (sign*(y2 - y) > 0)
1820 06 Feb 13 olle 563     {
1880 22 Feb 13 nicklas 564       y_new = y + sign*dash_size;
1820 06 Feb 13 olle 565       if (sign*y_new > sign*y2)
1820 06 Feb 13 olle 566       {
1820 06 Feb 13 olle 567         y_new = y2;
1820 06 Feb 13 olle 568       }
1820 06 Feb 13 olle 569       drawLine(x, y, x, y_new, context, xoff, yoff, wdt, hgt);
1880 22 Feb 13 nicklas 570       y = y_new + sign*space_size;
1820 06 Feb 13 olle 571     }
1820 06 Feb 13 olle 572   }
1820 06 Feb 13 olle 573    
1820 06 Feb 13 olle 574   /**
1820 06 Feb 13 olle 575    *  Draws a dotted horizontal line in a canvas context, using coordinates
1820 06 Feb 13 olle 576    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 577    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 578    */
1820 06 Feb 13 olle 579   function drawDottedLineHorizontal(x1, x2, y, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 580   {
1880 22 Feb 13 nicklas 581     var dash_step1 = 2;
1880 22 Feb 13 nicklas 582     var space_step1 = 4;
1820 06 Feb 13 olle 583     var sign = 1;
1820 06 Feb 13 olle 584     if (x1 > x2)
1820 06 Feb 13 olle 585     {
1820 06 Feb 13 olle 586       sign = -1;
1820 06 Feb 13 olle 587     }
1820 06 Feb 13 olle 588     var x = x1;
1820 06 Feb 13 olle 589     var x_new;
1820 06 Feb 13 olle 590     while (sign*(x2 - x) > 0)
1820 06 Feb 13 olle 591     {
1820 06 Feb 13 olle 592       x_new = x + sign*dash_step1;
1820 06 Feb 13 olle 593       if (sign*x_new > sign*x2)
1820 06 Feb 13 olle 594       {
1820 06 Feb 13 olle 595         x_new = x2;
1820 06 Feb 13 olle 596       }
1820 06 Feb 13 olle 597       drawLine(x, y, x_new, y, context, xoff, yoff, wdt, hgt);
1820 06 Feb 13 olle 598       x = x_new + sign*space_step1;
1820 06 Feb 13 olle 599     }
1820 06 Feb 13 olle 600   }
1820 06 Feb 13 olle 601    
1820 06 Feb 13 olle 602   /**
1820 06 Feb 13 olle 603    *  Draws text in a canvas context, using coordinates
1820 06 Feb 13 olle 604    *  where x increases to the right and y increases upwards,
1820 06 Feb 13 olle 605    *  and where the offsets are added automatically.
1820 06 Feb 13 olle 606    */
1820 06 Feb 13 olle 607   function drawText(text, x1, y1, context, xoff, yoff, wdt, hgt)
1820 06 Feb 13 olle 608   {
1880 22 Feb 13 nicklas 609     var x1_with_offset = fixCoordinate(context, x1 + xoff);
1880 22 Feb 13 nicklas 610     var y1_with_offset = fixCoordinate(context, yoff + hgt - y1);
1820 06 Feb 13 olle 611     context.fillText(text, x1_with_offset, y1_with_offset);
1820 06 Feb 13 olle 612   }
1820 06 Feb 13 olle 613
1820 06 Feb 13 olle 614   /**
1820 06 Feb 13 olle 615    *  Calculates an array of values for y-axis scale markers (ticks),
1820 06 Feb 13 olle 616    *  given the min and max values of the data, and the numbers
1820 06 Feb 13 olle 617    *  of markers (optional).
1820 06 Feb 13 olle 618    *
1820 06 Feb 13 olle 619    *  Based on PHP example on
1820 06 Feb 13 olle 620    *  http://stackoverflow.com/questions/326679/choosing-an-attractive-linear-scale-for-a-graphs-y-axis
1820 06 Feb 13 olle 621    *  but with modified behavior for step size < 1.
1820 06 Feb 13 olle 622    */
1820 06 Feb 13 olle 623   function calculateScaleMarkerArray(minDataValue, maxDataValue, numTicks)
1820 06 Feb 13 olle 624   {
1820 06 Feb 13 olle 625     if (numTicks == null || numTicks <= 0)
1820 06 Feb 13 olle 626     {
1820 06 Feb 13 olle 627       numTicks = 10;
1820 06 Feb 13 olle 628     }
1820 06 Feb 13 olle 629     // Adjust ticks if needed
1820 06 Feb 13 olle 630     if (numTicks < 2)
1820 06 Feb 13 olle 631     {
1820 06 Feb 13 olle 632       numTicks = 2;
1820 06 Feb 13 olle 633     }
1820 06 Feb 13 olle 634     else if (numTicks > 2)
1820 06 Feb 13 olle 635     {
1820 06 Feb 13 olle 636       numTicks -= 2;
1820 06 Feb 13 olle 637     }
1820 06 Feb 13 olle 638
1820 06 Feb 13 olle 639     // If min and max are identical, then adjust these values.
1820 06 Feb 13 olle 640     if (minDataValue == maxDataValue)
1820 06 Feb 13 olle 641     {
1820 06 Feb 13 olle 642       minDataValue = minDataValue - 10;
1820 06 Feb 13 olle 643       maxDataValue = maxDataValue + 10;
1820 06 Feb 13 olle 644     }
1820 06 Feb 13 olle 645
1820 06 Feb 13 olle 646     // Determine range
1820 06 Feb 13 olle 647     var range = maxDataValue - minDataValue;
1820 06 Feb 13 olle 648     // Get raw step value
1820 06 Feb 13 olle 649     var tempStep = range/numTicks;
1820 06 Feb 13 olle 650     // Calculate pretty step value
1820 06 Feb 13 olle 651     var mag = Math.floor(Math.log(tempStep)/Math.log(10) + 0.001);
1820 06 Feb 13 olle 652     var magPow = Math.pow(10, mag);
1820 06 Feb 13 olle 653     if (magPow >= 1)
1820 06 Feb 13 olle 654     {
1820 06 Feb 13 olle 655       magPow = Math.floor(magPow + 0.001);
1820 06 Feb 13 olle 656     }
1820 06 Feb 13 olle 657     var magMsd = Math.floor(tempStep/magPow + 0.5);
1820 06 Feb 13 olle 658     var stepSize = magMsd*magPow;
1820 06 Feb 13 olle 659     // Correction for stepSize < 1000
1820 06 Feb 13 olle 660     if (stepSize < 1000)
1820 06 Feb 13 olle 661     {
1820 06 Feb 13 olle 662       if (stepSize >= 750)
1820 06 Feb 13 olle 663       {
1820 06 Feb 13 olle 664         stepSize = 1000;
1820 06 Feb 13 olle 665       }
1820 06 Feb 13 olle 666       else if (stepSize >= 250)
1820 06 Feb 13 olle 667       {
1820 06 Feb 13 olle 668         stepSize = 500;
1820 06 Feb 13 olle 669       }
1820 06 Feb 13 olle 670       else if (stepSize >= 75)
1820 06 Feb 13 olle 671       {
1820 06 Feb 13 olle 672         stepSize = 100;
1820 06 Feb 13 olle 673       }
1820 06 Feb 13 olle 674       else if (stepSize >= 25)
1820 06 Feb 13 olle 675       {
1820 06 Feb 13 olle 676         stepSize = 50;
1820 06 Feb 13 olle 677       }
1820 06 Feb 13 olle 678       else if (stepSize >= 7.5)
1820 06 Feb 13 olle 679       {
1820 06 Feb 13 olle 680         stepSize = 10;
1820 06 Feb 13 olle 681       }
1820 06 Feb 13 olle 682       else if (stepSize >= 2.5)
1820 06 Feb 13 olle 683       {
1820 06 Feb 13 olle 684         stepSize = 5;
1820 06 Feb 13 olle 685       }
1820 06 Feb 13 olle 686       else if (stepSize >= 0.75)
1820 06 Feb 13 olle 687       {
1820 06 Feb 13 olle 688         stepSize = 1;
1820 06 Feb 13 olle 689       }
1820 06 Feb 13 olle 690       else if (stepSize >= 0.25)
1820 06 Feb 13 olle 691       {
1820 06 Feb 13 olle 692         stepSize = 0.5;
1820 06 Feb 13 olle 693       }
1820 06 Feb 13 olle 694       else if (stepSize >= 0.075)
1820 06 Feb 13 olle 695       {
1820 06 Feb 13 olle 696         stepSize = 0.1;
1820 06 Feb 13 olle 697       }
1820 06 Feb 13 olle 698       else if (stepSize >= 0.025)
1820 06 Feb 13 olle 699       {
1820 06 Feb 13 olle 700         stepSize = 0.05;
1820 06 Feb 13 olle 701       }
1820 06 Feb 13 olle 702       else if (stepSize >= 0.0075)
1820 06 Feb 13 olle 703       {
1820 06 Feb 13 olle 704         stepSize = 0.01;
1820 06 Feb 13 olle 705       }
1820 06 Feb 13 olle 706       else if (stepSize >= 0.0025)
1820 06 Feb 13 olle 707       {
1820 06 Feb 13 olle 708         stepSize = 0.005;
1820 06 Feb 13 olle 709       }
1820 06 Feb 13 olle 710       else if (stepSize >= 0.00075)
1820 06 Feb 13 olle 711       {
1820 06 Feb 13 olle 712         stepSize = 0.001;
1820 06 Feb 13 olle 713       }
1820 06 Feb 13 olle 714       else if (stepSize >= 0.00025)
1820 06 Feb 13 olle 715       {
1820 06 Feb 13 olle 716         stepSize = 0.0005;
1820 06 Feb 13 olle 717       }
1820 06 Feb 13 olle 718       else if (stepSize >= 0.000075)
1820 06 Feb 13 olle 719       {
1820 06 Feb 13 olle 720         stepSize = 0.0001;
1820 06 Feb 13 olle 721       }
1820 06 Feb 13 olle 722     }
1820 06 Feb 13 olle 723     
1820 06 Feb 13 olle 724     // Build y label array
1820 06 Feb 13 olle 725     // Lower and upper bounds calculations
1820 06 Feb 13 olle 726     var lowerBound = stepSize*Math.floor(minDataValue/stepSize);
1820 06 Feb 13 olle 727     var upperBound = stepSize*Math.ceil(maxDataValue/stepSize);
1820 06 Feb 13 olle 728     // Build array
1820 06 Feb 13 olle 729     var tickValuesArr = [];
1820 06 Feb 13 olle 730     var value = lowerBound;
1820 06 Feb 13 olle 731     var i = 0;
1820 06 Feb 13 olle 732     tickValuesArr[i] = value;
1820 06 Feb 13 olle 733     value += stepSize;
1820 06 Feb 13 olle 734     while(value <= upperBound)
1820 06 Feb 13 olle 735     {
1820 06 Feb 13 olle 736       i++;
1820 06 Feb 13 olle 737       tickValuesArr[i] = value;
1820 06 Feb 13 olle 738       value += stepSize;
1820 06 Feb 13 olle 739     }
1820 06 Feb 13 olle 740     return tickValuesArr;
1820 06 Feb 13 olle 741   }
1820 06 Feb 13 olle 742    
1820 06 Feb 13 olle 743   /**
1820 06 Feb 13 olle 744    *  Trims an array of values for y-axis scale markers (ticks),
1820 06 Feb 13 olle 745    *  to only include values in given value range.
1820 06 Feb 13 olle 746    */
1820 06 Feb 13 olle 747   function trimScaleMarkerArray(origTickValuesArr, minValue, maxValue)
1820 06 Feb 13 olle 748   {
1820 06 Feb 13 olle 749     // Build trimmed y label array
1820 06 Feb 13 olle 750     var tickValuesArr = [];
1820 06 Feb 13 olle 751     var value;
1820 06 Feb 13 olle 752     var k = 0;
1820 06 Feb 13 olle 753     for (i=0; i < origTickValuesArr.length; i++)
1820 06 Feb 13 olle 754     {
1820 06 Feb 13 olle 755       value = origTickValuesArr[i];
1820 06 Feb 13 olle 756       if (value >= minValue && value <= maxValue)
1820 06 Feb 13 olle 757       {
1820 06 Feb 13 olle 758         // Add value to trimmed array
1820 06 Feb 13 olle 759         tickValuesArr[k] = value;
1820 06 Feb 13 olle 760         k++;
1820 06 Feb 13 olle 761       }
1820 06 Feb 13 olle 762     }
1820 06 Feb 13 olle 763     return tickValuesArr;
1820 06 Feb 13 olle 764   }
1880 22 Feb 13 nicklas 765   
1880 22 Feb 13 nicklas 766   /*
1880 22 Feb 13 nicklas 767     Fixes a plotting coordinate by aligning it so that anti-aliasing
1880 22 Feb 13 nicklas 768     is avoided. For 1-pixel line widths the coordate should alway be a 
1880 22 Feb 13 nicklas 769     half-value, eg. 1.5, 10.5, etc.
1880 22 Feb 13 nicklas 770   */
1880 22 Feb 13 nicklas 771   function fixCoordinate(context, value)
1880 22 Feb 13 nicklas 772   {
1880 22 Feb 13 nicklas 773     return Math.floor(value) + (context.lineWidth % 2) / 2;
1880 22 Feb 13 nicklas 774   }
1880 22 Feb 13 nicklas 775