1/** 2 * -------------------------------------------------------------------- 3 * jQuery-Plugin "visualize" 4 * by Scott Jehl, [email protected] 5 * http://www.filamentgroup.com 6 * Copyright (c) 2009 Filament Group 7 * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. 8 * 9 * -------------------------------------------------------------------- 10 */ 11(function($) { 12$.fn.visualize = function(options, container){ 13 return $(this).each(function(){ 14 //configuration 15 var o = $.extend({ 16 type: 'bar', //also available: area, pie, line 17 width: $(this).width(), //height of canvas - defaults to table height 18 height: $(this).height(), //height of canvas - defaults to table height 19 appendTitle: true, //table caption text is added to chart 20 title: null, //grabs from table caption if null 21 appendKey: true, //color key is added to chart 22 colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'], 23 textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS 24 parseDirection: 'x', //which direction to parse the table data 25 pieMargin: 20, //pie charts only - spacing around pie 26 pieLabelPos: 'inside', 27 lineWeight: 4, //for line and area - stroke weight 28 barGroupMargin: 10, 29 barMargin: 1, //space around bars in bar chart (added to both sides of bar) 30 yLabelInterval: 30, //distance between y labels 31 topValue: false, 32 bottomValue: false, 33 },options); 34 35 //reset width, height to numbers 36 o.width = parseFloat(o.width); 37 o.height = parseFloat(o.height); 38 39 40 var self = $(this); 41 42 //function to scrape data from html table 43 function scrapeTable(){ 44 var colors = o.colors; 45 var textColors = o.textColors; 46 var tableData = { 47 dataGroups: function(){ 48 var dataGroups = []; 49 if(o.parseDirection == 'x'){ 50 self.find('tr:gt(0)').each(function(i){ 51 dataGroups[i] = {}; 52 dataGroups[i].points = []; 53 dataGroups[i].color = colors[i]; 54 if(textColors[i]){ dataGroups[i].textColor = textColors[i]; } 55 $(this).find('td').each(function(){ 56 dataGroups[i].points.push( parseFloat($(this).text()) ); 57 }); 58 }); 59 } 60 else { 61 var cols = self.find('tr:eq(1) td').size(); 62 for(var i=0; i<cols; i++){ 63 dataGroups[i] = {}; 64 dataGroups[i].points = []; 65 dataGroups[i].color = colors[i]; 66 if(textColors[i]){ dataGroups[i].textColor = textColors[i]; } 67 self.find('tr:gt(0)').each(function(){ 68 dataGroups[i].points.push( $(this).find('td').eq(i).text()*1 ); 69 }); 70 }; 71 } 72 return dataGroups; 73 }, 74 allData: function(){ 75 var allData = []; 76 $(this.dataGroups()).each(function(){ 77 allData.push(this.points); 78 }); 79 return allData; 80 }, 81 dataSum: function(){ 82 var dataSum = 0; 83 var allData = this.allData().join(',').split(','); 84 $(allData).each(function(){ 85 dataSum += parseFloat(this); 86 }); 87 return dataSum 88 }, 89 topValue: function(){ 90 if(o.topValue) return o.topValue; 91 92 var topValue = 0; 93 var allData = this.allData().join(',').split(','); 94 $(allData).each(function(){ 95 if(parseFloat(this,10)>topValue) topValue = parseFloat(this); 96 }); 97 return topValue; 98 }, 99 bottomValue: function(){ 100 if(o.bottomValue) return o.bottomValue; 101 102 var bottomValue = 0; 103 var allData = this.allData().join(',').split(','); 104 $(allData).each(function(){ 105 if(this<bottomValue) bottomValue = parseFloat(this); 106 }); 107 return bottomValue; 108 }, 109 memberTotals: function(){ 110 var memberTotals = []; 111 var dataGroups = this.dataGroups(); 112 $(dataGroups).each(function(l){ 113 var count = 0; 114 $(dataGroups[l].points).each(function(m){ 115 count +=dataGroups[l].points[m]; 116 }); 117 memberTotals.push(count); 118 }); 119 return memberTotals; 120 }, 121 yTotals: function(){ 122 var yTotals = []; 123 var dataGroups = this.dataGroups(); 124 var loopLength = this.xLabels().length; 125 for(var i = 0; i<loopLength; i++){ 126 yTotals[i] =[]; 127 var thisTotal = 0; 128 $(dataGroups).each(function(l){ 129 yTotals[i].push(this.points[i]); 130 }); 131 yTotals[i].join(',').split(','); 132 $(yTotals[i]).each(function(){ 133 thisTotal += parseFloat(this); 134 }); 135 yTotals[i] = thisTotal; 136 137 } 138 return yTotals; 139 }, 140 topYtotal: function(){ 141 var topYtotal = 0; 142 var yTotals = this.yTotals().join(',').split(','); 143 $(yTotals).each(function(){ 144 if(parseFloat(this,10)>topYtotal) topYtotal = parseFloat(this); 145 }); 146 return topYtotal; 147 }, 148 totalYRange: function(){ 149 return this.topValue() - this.bottomValue(); 150 }, 151 xLabels: function(){ 152 var xLabels = []; 153 if(o.parseDirection == 'x'){ 154 self.find('tr:eq(0) th').each(function(){ 155 xLabels.push($(this).html()); 156 }); 157 } 158 else { 159 self.find('tr:gt(0) th').each(function(){ 160 xLabels.push($(this).html()); 161 }); 162 } 163 return xLabels; 164 }, 165 yLabels: function(){ 166 var yLabels = []; 167 yLabels.push(bottomValue); 168 var numLabels = Math.round(o.height / o.yLabelInterval);
169 var loopInterval = Math.ceil(totalYRange / numLabels) || 1; 170 while( yLabels[yLabels.length-1] < topValue - loopInterval){ 171 yLabels.push(yLabels[yLabels.length-1] + loopInterval); 172 } 173 yLabels.push(topValue); 174 return yLabels; 175 } 176 }; 177 178 return tableData; 179 }; 180 181 182 //function to create a chart 183 var createChart = { 184 pie: function(){ 185 186 canvasContain.addClass('visualize-pie'); 187 188 if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); } 189 190 var centerx = Math.round(canvas.width()/2); 191 var centery = Math.round(canvas.height()/2); 192 var radius = centery - o.pieMargin; 193 var counter = 0.0; 194 var toRad = function(integer){ return (Math.PI/180)*integer; }; 195 var labels = $('<ul class="visualize-labels"></ul>') 196 .insertAfter(canvas); 197 198 //draw the pie pieces 199 $.each(memberTotals, function(i){ 200 var fraction = (this <= 0 || isNaN(this))? 0 : this / dataSum; 201 ctx.beginPath(); 202 ctx.moveTo(centerx, centery); 203 ctx.arc(centerx, centery, radius, 204 counter * Math.PI * 2 - Math.PI * 0.5, 205 (counter + fraction) * Math.PI * 2 - Math.PI * 0.5, 206 false); 207 ctx.lineTo(centerx, centery); 208 ctx.closePath(); 209 ctx.fillStyle = dataGroups[i].color; 210 ctx.fill(); 211 // draw labels 212 var sliceMiddle = (counter + fraction/2);
213 var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius + radius / 5; 214 var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance)); 215 var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance)); 216 var leftRight = (labelx > centerx) ? 'right' : 'left'; 217 var topBottom = (labely > centery) ? 'bottom' : 'top'; 218 var labeltext = $('<span class="visualize-label">' + Math.round(fraction*100) + '%</span>') 219 .css(leftRight, 0) 220 .css(topBottom, 0); 221 var label = $('<li class="visualize-label-pos"></li>') 222 .appendTo(labels) 223 .css({left: labelx, top: labely}) 224 .append(labeltext); 225 labeltext 226 .css('font-size', radius / 8) 227 .css('margin-'+leftRight, -labeltext.width()/2) 228 .css('margin-'+topBottom, -labeltext.outerHeight()/2); 229 230 if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); } 231 counter+=fraction; 232 }); 233 }, 234 235 line: function(area){ 236 237 if(area){ canvasContain.addClass('visualize-area'); } 238 else{ canvasContain.addClass('visualize-line'); } 239 240 //write X labels 241 var xInterval = canvas.width() / (xLabels.length -1); 242 var xlabelsUL = $('<ul class="visualize-labels-x"></ul>') 243 .width(canvas.width()) 244 .height(canvas.height()) 245 .insertBefore(canvas); 246 $.each(xLabels, function(i){ 247 var thisLi = $('<li><span>'+this+'</span></li>') 248 .prepend('<span class="line" />') 249 .css('left', xInterval * i) 250 .appendTo(xlabelsUL); 251 var label = thisLi.find('span:not(.line)'); 252 var leftOffset = label.width()/-2; 253 if(i == 0){ leftOffset = 0; } 254 else if(i== xLabels.length-1){ leftOffset = -label.width(); } 255 label 256 .css('margin-left', leftOffset) 257 .addClass('label'); 258 }); 259 260 //write Y labels 261 var yScale = canvas.height() / totalYRange; 262 var liBottom = canvas.height() / (yLabels.length-1); 263 var ylabelsUL = $('<ul class="visualize-labels-y"></ul>') 264 .width(canvas.width()) 265 .height(canvas.height()) 266 .insertBefore(canvas); 267 268 $.each(yLabels, function(i){ 269 var thisLi = $('<li><span>'+this+'</span></li>') 270 .prepend('<span class="line" />') 271 .css('bottom',liBottom*i) 272 .prependTo(ylabelsUL); 273 var label = thisLi.find('span:not(.line)'); 274 var topOffset = label.height()/-2; 275 if(i == 0){ topOffset = -label.height(); } 276 else if(i== yLabels.length-1){ topOffset = 0; } 277 label 278 .css('margin-top', topOffset) 279 .addClass('label'); 280 }); 281 282 //start from the bottom left 283 ctx.translate(0,zeroLoc); 284 //iterate and draw 285 $.each(dataGroups,function(h){ 286 ctx.beginPath(); 287 ctx.lineWidth = o.lineWeight; 288 ctx.lineJoin = 'round'; 289 var points = this.points; 290 var integer = 0; 291 ctx.moveTo(0,-(points[0]*yScale)); 292 $.each(points, function(){ 293 ctx.lineTo(integer,-(this*yScale)); 294 integer+=xInterval; 295 }); 296 ctx.strokeStyle = this.color; 297 ctx.stroke(); 298 if(area){ 299 ctx.lineTo(integer,0); 300 ctx.lineTo(0,0); 301 ctx.closePath(); 302 ctx.fillStyle = this.color; 303 ctx.globalAlpha = .3; 304 ctx.fill(); 305 ctx.globalAlpha = 1.0; 306 } 307 else {ctx.closePath();} 308 }); 309 }, 310 311 area: function(){ 312 createChart.line(true); 313 }, 314 315 bar: function(){ 316 317 canvasContain.addClass('visualize-bar'); 318 319 //write X labels 320 var xInterval = canvas.width() / (xLabels.length); 321 var xlabelsUL = $('<ul class="visualize-labels-x"></ul>') 322 .width(canvas.width()) 323 .height(canvas.height()) 324 .insertBefore(canvas); 325 $.each(xLabels, function(i){ 326 var thisLi = $('<li><span class="label">'+this+'</span></li>') 327 .prepend('<span class="line" />') 328 .css('left', xInterval * i) 329 .width(xInterval) 330 .appendTo(xlabelsUL); 331 var label = thisLi.find('span.label'); 332 label.addClass('label'); 333 }); 334 335 //write Y labels 336 var yScale = canvas.height() / totalYRange; 337 var liBottom = canvas.height() / (yLabels.length-1); 338 var ylabelsUL = $('<ul class="visualize-labels-y"></ul>') 339 .width(canvas.width()) 340 .height(canvas.height()) 341 .insertBefore(canvas); 342 $.each(yLabels, function(i){ 343 var thisLi = $('<li><span>'+this+'</span></li>') 344 .prepend('<span class="line" />') 345 .css('bottom',liBottom*i) 346 .prependTo(ylabelsUL); 347 var label = thisLi.find('span:not(.line)'); 348 var topOffset = label.height()/-2; 349 if(i == 0){ topOffset = -label.height(); } 350 else if(i== yLabels.length-1){ topOffset = 0; } 351 label 352 .css('margin-top', topOffset) 353 .addClass('label'); 354 }); 355 356 //start from the bottom left 357 ctx.translate(0,zeroLoc); 358 //iterate and draw
359 for(var h=0; h<dataGroups.length; h++){ 360 ctx.beginPath(); 361 var linewidth = (xInterval-o.barGroupMargin*2) / dataGroups.length; //removed +1 362 var strokeWidth = linewidth - (o.barMargin*2); 363 ctx.lineWidth = strokeWidth; 364 var points = dataGroups[h].points; 365 var integer = 0; 366 for(var i=0; i<points.length; i++){ 367 var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2; 368 xVal += o.barGroupMargin*2; 369 370 ctx.moveTo(xVal, 0); 371 ctx.lineTo(xVal, Math.round(-points[i]*yScale)); 372 integer+=xInterval; 373 } 374 ctx.strokeStyle = dataGroups[h].color; 375 ctx.stroke(); 376 ctx.closePath(); 377 } 378 } 379 }; 380 381 //create new canvas, set w&h attrs (not inline styles) 382 var canvasNode = document.createElement("canvas"); 383 canvasNode.setAttribute('height',o.height); 384 canvasNode.setAttribute('width',o.width); 385 var canvas = $(canvasNode); 386 387 //get title for chart 388 var title = o.title || self.find('caption').text(); 389 390 //create canvas wrapper div, set inline w&h, append 391 var canvasContain = (container || $('<div class="visualize" role="img" aria-label="Chart representing data from the table: '+ title +'" />')) 392 .height(o.height) 393 .width(o.width) 394 .append(canvas); 395 396 //scrape table (this should be cleaned up into an obj) 397 var tableData = scrapeTable(); 398 var dataGroups = tableData.dataGroups(); 399 var allData = tableData.allData(); 400 var dataSum = tableData.dataSum(); 401 var topValue = tableData.topValue(); 402 var bottomValue = tableData.bottomValue(); 403 var memberTotals = tableData.memberTotals(); 404 var totalYRange = tableData.totalYRange(); 405 var zeroLoc = o.height * (topValue/totalYRange); 406 var xLabels = tableData.xLabels(); 407 var yLabels = tableData.yLabels(); 408 409 //title/key container 410 if(o.appendTitle || o.appendKey){ 411 var infoContain = $('<div class="visualize-info"></div>') 412 .appendTo(canvasContain); 413 } 414 415 //append title 416 if(o.appendTitle){ 417 $('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain); 418 } 419 420 421 //append key 422 if(o.appendKey){ 423 var newKey = $('<ul class="visualize-key"></ul>'); 424 var selector = (o.parseDirection == 'x') ? 'tr:gt(0) th' : 'tr:eq(0) th' ; 425 self.find(selector).each(function(i){ 426 $('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ $(this).text() +'</span></li>') 427 .appendTo(newKey); 428 }); 429 newKey.appendTo(infoContain); 430 }; 431 432 //append new canvas to page 433 434 if(!container){canvasContain.insertAfter(this); } 435 if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.initElement(canvas[0]); } 436 437 //set up the drawing board 438 var ctx = canvas[0].getContext('2d'); 439 440 //create chart 441 createChart[o.type](); 442 443 //clean up some doubled lines that sit on top of canvas borders (done via JS due to IE) 444 $('.visualize-line li:first-child span.line, .visualize-line li:last-child span.line, .visualize-area li:first-child span.line, .visualize-area li:last-child span.line, .visualize-bar li:first-child span.line,.visualize-bar .visualize-labels-y li:last-child span.line').css('border','none'); 445 if(!container){ 446 //add event for updating 447 canvasContain.bind('visualizeRefresh', function(){ 448 self.visualize(o, $(this).empty()); 449 }); 450 } 451 }).next(); //returns canvas(es) 452}; 453})(jQuery); 454
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.