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

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