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: ['#ae432e','#77ab13','#058dc7','#ef561a','#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: 10, //pie charts only - spacing around pie 26 pieLabelsAsPercent: true, 27 pieLabelPos: 'inside', 28 lineWeight: 4, //for line and area - stroke weight 29 lineDots: false, //also available: 'single', 'double' 30 dotInnerColor: "#ffffff", // only used for lineDots:'double' 31 lineMargin: (options.lineDots?15:0), //for line and area - spacing around lines 32 barGroupMargin: 10, 33 chartId: '', 34 xLabelParser: null, // function to parse labels as values 35 valueParser: null, // function to parse values. must return a Number 36 chartId: '', 37 chartClass: '', 38 barMargin: 1, //space around bars in bar chart (added to both sides of bar) 39 yLabelInterval: 30, //distance between y labels 40 interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table 41 },options); 42 43 //reset width, height to numbers 44 o.width = parseFloat(o.width); 45 o.height = parseFloat(o.height); 46 47 // reset padding if graph is not lines 48 if(o.type != 'line' && o.type != 'area' ) { 49 o.lineMargin = 0; 50 } 51 52 var self = $(this); 53 54 // scrape data from html table 55 var tableData = {}; 56 var colors = o.colors; 57 var textColors = o.textColors; 58 59 60 var parseLabels = function(direction){ 61 var labels = []; 62 if(direction == 'x'){ 63 self.find('thead tr').each(function(i){ 64 $(this).find('th').each(function(j){ 65 if(!labels[j]) { 66 labels[j] = []; 67 } 68 labels[j][i] = $(this).text() 69 }) 70 }); 71 } 72 else { 73 self.find('tbody tr').each(function(i){ 74 $(this).find('th').each(function(j) { 75 if(!labels[i]) { 76 labels[i] = []; 77 } 78 labels[i][j] = $(this).text() 79 }); 80 }); 81 } 82 return labels; 83 }; 84 85 var fnParse = o.valueParser || parseFloat; 86 var dataGroups = tableData.dataGroups = []; 87 if(o.parseDirection == 'x'){ 88 self.find('tbody tr').each(function(i,tr){ 89 dataGroups[i] = {}; 90 dataGroups[i].points = []; 91 dataGroups[i].color = colors[i]; 92 if(textColors[i]){ dataGroups[i].textColor = textColors[i]; } 93 $(tr).find('td').each(function(j,td){ 94 dataGroups[i].points.push( { 95 value: fnParse($(td).text()), 96 elem: td,
97 tableCords: [i,j] 98 } ); 99 }); 100 }); 101 } else { 102 var cols = self.find('tbody tr:eq(0) td').size(); 103 for(var i=0; i<cols; i++){ 104 dataGroups[i] = {}; 105 dataGroups[i].points = []; 106 dataGroups[i].color = colors[i]; 107 if(textColors[i]){ dataGroups[i].textColor = textColors[i]; } 108 self.find('tbody tr').each(function(j){ 109 dataGroups[i].points.push( { 110 value: $(this).find('td').eq(i).text()*1, 111 elem: this, 112 tableCords: [i,j] 113 } ); 114 }); 115 }; 116 } 117 118 119 var allItems = tableData.allItems = []; 120 $(dataGroups).each(function(i,row){ 121 var count = 0; 122 $.each(row.points,function(j,point){ 123 allItems.push(point); 124 count += point.value; 125 }); 126 row.groupTotal = count; 127 }); 128 129 tableData.dataSum = 0; 130 tableData.topValue = 0; 131 tableData.bottomValue = Infinity; 132 $.each(allItems,function(i,item){ 133 tableData.dataSum += fnParse(item.value); 134 if(fnParse(item.value,10)>tableData.topValue) { 135 tableData.topValue = fnParse(item.value,10); 136 } 137 if(item.value<tableData.bottomValue) { 138 tableData.bottomValue = fnParse(item.value); 139 } 140 }); 141 var dataSum = tableData.dataSum; 142 var topValue = tableData.topValue; 143 var bottomValue = tableData.bottomValue; 144 145 var xAllLabels = tableData.xAllLabels = parseLabels(o.parseDirection); 146 var yAllLabels = tableData.yAllLabels = parseLabels(o.parseDirection==='x'?'y':'x'); 147 148 var xLabels = tableData.xLabels = []; 149 $.each(tableData.xAllLabels,function(i,labels) { 150 tableData.xLabels.push(labels[0]); 151 }); 152 153 var totalYRange = tableData.totalYRange = tableData.topValue - tableData.bottomValue; 154 155 var zeroLocX = tableData.zeroLocX = 0; 156 157 if($.isFunction(o.xLabelParser)) { 158 159 var xTopValue = null; 160 var xBottomValue = null; 161 162 $.each(xLabels,function(i,label) { 163 label = xLabels[i] = o.xLabelParser(label); 164 if(i === 0) { 165 xTopValue = label; 166 xBottomValue = label; 167 } 168 if(label>xTopValue) { 169 xTopValue = label; 170 } 171 if(label<xBottomValue) { 172 xBottomValue = label; 173 } 174 }); 175 176 var totalXRange = tableData.totalXRange = xTopValue - xBottomValue; 177 178 179 var xScale = tableData.xScale = (o.width -2*o.lineMargin) / totalXRange; 180 var marginDiffX = 0; 181 if(o.lineMargin) { 182 var marginDiffX = -2*xScale-o.lineMargin; 183 } 184 zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin; 185 186 tableData.xBottomValue = xBottomValue; 187 tableData.xTopValue = xTopValue; 188 tableData.totalXRange = totalXRange; 189 } 190 191 var yScale = tableData.yScale = (o.height - 2*o.lineMargin) / totalYRange; 192 var zeroLocY = tableData.zeroLocY = (o.height-2*o.lineMargin) * (tableData.topValue/tableData.totalYRange) + o.lineMargin; 193 194 var yLabels = tableData.yLabels = []; 195 196 var numLabels = Math.floor((o.height - 2*o.lineMargin) / 30); 197 var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab 198 loopInterval = Math.round(parseFloat(loopInterval)/5)*5; 199 loopInterval = Math.max(loopInterval, 1); 200 // var start = 201 for(var j=Math.round(parseInt(tableData.bottomValue)/5)*5; j<=tableData.topValue+loopInterval; j+=loopInterval){ 202 yLabels.push(j); 203 } 204 if(yLabels[yLabels.length-1] > tableData.topValue+loopInterval) { 205 yLabels.pop(); 206 } else if (yLabels[yLabels.length-1] <= tableData.topValue-10) { 207 yLabels.push(tableData.topValue); 208 } 209 210 // populate some data 211 $.each(dataGroups,function(i,row){ 212 row.yLabels = tableData.yAllLabels[i]; 213 $.each(row.points, function(j,point){ 214 point.zeroLocY = tableData.zeroLocY; 215 point.zeroLocX = tableData.zeroLocX; 216 point.xLabels = tableData.xAllLabels[j]; 217 point.yLabels = tableData.yAllLabels[i]; 218 point.color = row.color; 219 }); 220 }); 221 222 try{console.log(tableData);}catch(e){} 223 224 var charts = {}; 225 226 charts.pie = { 227 interactionPoints: dataGroups, 228 229 setup: function() { 230 charts.pie.draw(true); 231 }, 232 draw: function(drawHtml){ 233 234 var centerx = Math.round(canvas.width()/2); 235 var centery = Math.round(canvas.height()/2); 236 var radius = centery - o.pieMargin; 237 var counter = 0.0; 238 239 if(drawHtml) { 240 canvasContain.addClass('visualize-pie'); 241 242 if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); } 243 244 var toRad = function(integer){ return (Math.PI/180)*integer; }; 245 var labels = $('<ul class="visualize-labels"></ul>') 246 .insertAfter(canvas); 247 } 248 249 250 //draw the pie pieces 251 $.each(dataGroups, function(i,row){ 252 var fraction = row.groupTotal / dataSum; 253 if (fraction <= 0 || isNaN(fraction)) 254 return; 255 ctx.beginPath(); 256 ctx.moveTo(centerx, centery); 257 ctx.arc(centerx, centery, radius, 258 counter * Math.PI * 2 - Math.PI * 0.5, 259 (counter + fraction) * Math.PI * 2 - Math.PI * 0.5, 260 false); 261 ctx.lineTo(centerx, centery); 262 ctx.closePath(); 263 ctx.fillStyle = dataGroups[i].color; 264 ctx.fill(); 265 // draw labels 266 if(drawHtml) { 267 var sliceMiddle = (counter + fraction/2);
268 var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius + radius / 5; 269 var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance)); 270 var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance)); 271 var leftRight = (labelx > centerx) ? 'right' : 'left'; 272 var topBottom = (labely > centery) ? 'bottom' : 'top'; 273 var percentage = parseFloat((fraction*100).toFixed(2)); 274 275 // interaction variables 276 row.canvasCords = [labelx,labely]; 277 row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API 278 row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API 279 row.value = row.groupTotal; 280 281 282 if(percentage){ 283 var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal; 284 var labeltext = $('<span class="visualize-label">' + labelval +'</span>') 285 .css(leftRight, 0) 286 .css(topBottom, 0); 287 if(labeltext) 288 var label = $('<li class="visualize-label-pos"></li>') 289 .appendTo(labels) 290 .css({left: labelx, top: labely}) 291 .append(labeltext); 292 labeltext 293 .css('font-size', radius / 8) 294 .css('margin-'+leftRight, -labeltext.width()/2) 295 .css('margin-'+topBottom, -labeltext.outerHeight()/2); 296 297 if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); } 298 299 } 300 } 301 counter+=fraction; 302 }); 303 } 304 }; 305 306 (function(){ 307 308 var xInterval; 309 310 var drawPoint = function (ctx,x,y,color,size) { 311 ctx.moveTo(x,y); 312 ctx.beginPath(); 313 ctx.arc(x,y,size/2,0,2*Math.PI,false); 314 ctx.closePath(); 315 ctx.fillStyle = color; 316 ctx.fill(); 317 }; 318 319 charts.line = { 320 321 interactionPoints: allItems, 322 323 setup: function(area){ 324 325 if(area){ canvasContain.addClass('visualize-area'); } 326 else{ canvasContain.addClass('visualize-line'); } 327 328 //write X labels 329 var xlabelsUL = $('<ul class="visualize-labels-x"></ul>') 330 .width(canvas.width()) 331 .height(canvas.height()) 332 .insertBefore(canvas); 333 334 if(!o.customXLabels) { 335 xInterval = (canvas.width() - 2*o.lineMargin) / (xLabels.length -1); 336 $.each(xLabels, function(i){ 337 var thisLi = $('<li><span>'+this+'</span></li>') 338 .prepend('<span class="line" />') 339 .css('left', o.lineMargin + xInterval * i) 340 .appendTo(xlabelsUL); 341 var label = thisLi.find('span:not(.line)'); 342 var leftOffset = label.width()/-2; 343 if(i == 0){ leftOffset = 0; } 344 else if(i== xLabels.length-1){ leftOffset = -label.width(); } 345 label 346 .css('margin-left', leftOffset) 347 .addClass('label'); 348 }); 349 } else { 350 o.customXLabels(tableData,xlabelsUL); 351 } 352 353 //write Y labels 354 var liBottom = (canvas.height() - 2*o.lineMargin) / (yLabels.length-1); 355 var ylabelsUL = $('<ul class="visualize-labels-y"></ul>') 356 .width(canvas.width()) 357 .height(canvas.height()) 358 // .css('margin-top',-o.lineMargin) 359 .insertBefore(scroller); 360 361 $.each(yLabels, function(i){ 362 var value = Math.floor(this); 363 var posB = (value-bottomValue)*yScale + o.lineMargin; 364 if(posB >= o.height-1 || posB < 0) { 365 return; 366 } 367 var thisLi = $('<li><span>'+value+'</span></li>') 368 .css('bottom', posB); 369 if(Math.abs(posB) < o.height-1) { 370 thisLi.prepend('<span class="line" />'); 371 } 372 thisLi.prependTo(ylabelsUL); 373 374 var label = thisLi.find('span:not(.line)'); 375 var topOffset = label.height()/-2; 376 if(!o.lineMargin) { 377 if(i == 0){ topOffset = -label.height(); } 378 else if(i== yLabels.length-1){ topOffset = 0; } 379 } 380 label 381 .css('margin-top', topOffset) 382 .addClass('label'); 383 }); 384 385 //start from the bottom left 386 ctx.translate(zeroLocX,zeroLocY); 387 388 charts.line.draw(area); 389 390 }, 391 392 draw: function(area) { 393 // prevent drawing on top of previous draw 394 ctx.clearRect(-zeroLocX,-zeroLocY,o.width,o.height); 395 // Calculate each point properties before hand 396 var integer; 397 $.each(dataGroups,function(i,row){ 398 integer = o.lineMargin; // the current offset 399 $.each(row.points, function(j,point){ 400 if(o.xLabelParser) {
401 point.canvasCords = [(xLabels[j]-zeroLocX)*xScale - xBottomValue,-(point.value*yScale)]; 402 } else { 403 point.canvasCords = [integer,-(point.value*yScale)]; 404 } 405 406 if(o.lineDots) { 407 point.dotSize = o.dotSize||o.lineWeight*Math.PI; 408 point.dotInnerSize = o.dotInnerSize||o.lineWeight*Math.PI/2; 409 if(o.lineDots == 'double') { 410 point.innerColor = o.dotInnerColor; 411 } 412 } 413 integer+=xInterval; 414 }); 415 }); 416 // fire custom event so we can enable rich interaction 417 self.trigger('vizualizeBeforeDraw',{options:o,table:self,canvasContain:canvasContain,tableData:tableData}); 418 // draw lines and areas 419 $.each(dataGroups,function(h){ 420 // Draw lines 421 ctx.beginPath(); 422 ctx.lineWidth = o.lineWeight; 423 ctx.lineJoin = 'round'; 424 $.each(this.points, function(g){ 425 var loc = this.canvasCords; 426 if(g == 0) { 427 ctx.moveTo(loc[0],loc[1]); 428 } 429 ctx.lineTo(loc[0],loc[1]); 430 }); 431 ctx.strokeStyle = this.color; 432 ctx.stroke(); 433 // Draw fills 434 if(area){ 435 var integer = this.points[this.points.length-1].canvasCords[0]; 436 if (isFinite(integer)) 437 ctx.lineTo(integer,0); 438 ctx.lineTo(o.lineMargin,0); 439 ctx.closePath(); 440 ctx.fillStyle = this.color; 441 ctx.globalAlpha = .3; 442 ctx.fill(); 443 ctx.globalAlpha = 1.0; 444 } 445 else {ctx.closePath();} 446 }); 447 // draw points 448 if(o.lineDots) { 449 $.each(dataGroups,function(h){ 450 $.each(this.points, function(g){ 451 drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.color,this.dotSize); 452 if(o.lineDots === 'double') { 453 drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.innerColor,this.dotInnerSize); 454 } 455 }); 456 }); 457 } 458 459 } 460 }; 461 462 })(); 463 464 charts.area = { 465 setup: function() { 466 charts.line.setup(true); 467 }, 468 draw: charts.line.draw 469 }; 470 471 (function(){ 472 473 var horizontal,bottomLabels; 474 475 charts.bar = { 476 setup:function(){ 477 /** 478 * We can draw horizontal or vertical bars depending on the 479 * value of the 'barDirection' option (which may be 'vertical' or 480 * 'horizontal'). 481 */ 482 483 horizontal = (o.barDirection == 'horizontal'); 484 485 canvasContain.addClass('visualize-bar'); 486 487 /** 488 * Write labels along the bottom of the chart. If we're drawing 489 * horizontal bars, these will be the yLabels, otherwise they 490 * will be the xLabels. The positioning also varies slightly: 491 * yLabels are values, hence they will span the whole width of 492 * the canvas, whereas xLabels are supposed to line up with the 493 * bars. 494 */ 495 bottomLabels = horizontal ? yLabels : xLabels; 496 497 var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0)); 498 499 var xlabelsUL = $('<ul class="visualize-labels-x"></ul>') 500 .width(canvas.width()) 501 .height(canvas.height()) 502 .insertBefore(canvas); 503 504 $.each(bottomLabels, function(i){ 505 var thisLi = $('<li><span class="label">'+this+'</span></li>') 506 .prepend('<span class="line" />') 507 .css('left', xInterval * i) 508 .width(xInterval) 509 .appendTo(xlabelsUL); 510 511 if (horizontal) { 512 var label = thisLi.find('span.label'); 513 label.css("margin-left", -label.width() / 2); 514 } 515 }); 516 517 /** 518 * Write labels along the left of the chart. Follows the same idea 519 * as the bottom labels. 520 */ 521 var leftLabels = horizontal ? xLabels : yLabels; 522 var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1)); 523 524 var ylabelsUL = $('<ul class="visualize-labels-y"></ul>') 525 .width(canvas.width()) 526 .height(canvas.height()) 527 .insertBefore(canvas); 528 529 $.each(leftLabels, function(i){ 530 var thisLi = $('<li><span>'+this+'</span></li>').prependTo(ylabelsUL); 531 532 var label = thisLi.find('span:not(.line)').addClass('label'); 533 534 if (horizontal) { 535 /** 536 * For left labels, we want to vertically align the text 537 * to the middle of its container, but we don't know how 538 * many lines of text we will have, since the labels could 539 * be very long. 540 * 541 * So we set a min-height of liBottom, and a max-height 542 * of liBottom + 1, so we can then check the label's actual 543 * height to determine if it spans one line or more lines. 544 */ 545 label.css({ 546 'min-height': liBottom, 547 'max-height': liBottom + 1, 548 'vertical-align': 'middle' 549 });
550 thisLi.css({'top': liBottom * i, 'min-height': liBottom}); 551 552 var r = label[0].getClientRects()[0]; 553 if (r.bottom - r.top == liBottom) { 554 /* This means we have only one line of text; hence 555 * we can centre the text vertically by setting the line-height, 556 * as described at: 557 * http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html 558 * 559 * (Although firefox has .height on the rectangle, IE doesn't, 560 * so we use r.bottom - r.top rather than r.height.) 561 */ 562 label.css('line-height', parseInt(liBottom) + 'px'); 563 } 564 else { 565 /* 566 * If there is more than one line of text, then we shouldn't 567 * touch the line height, but we should make sure the text 568 * doesn't overflow the container. 569 */ 570 label.css("overflow", "hidden"); 571 } 572 } 573 else { 574 thisLi.css('bottom', liBottom * i).prepend('<span class="line" />'); 575 label.css('margin-top', -label.height() / 2) 576 } 577 }); 578 579 charts.bar.draw(); 580 581 }, 582 583 draw: function() { 584 // Draw bars 585 586 if (horizontal) { 587 // for horizontal, keep the same code, but rotate everything 90 degrees 588 // clockwise. 589 ctx.rotate(Math.PI / 2); 590 } 591 else { 592 // for vertical, translate to the top left corner. 593 ctx.translate(0, zeroLocY); 594 } 595 596 // Don't attempt to draw anything if all the values are zero, 597 // otherwise we will get weird exceptions from the canvas methods. 598 if (totalYRange <= 0) 599 return; 600 601 var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange; 602 var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length)); 603 var linewidth = (barWidth - o.barGroupMargin*2) / dataGroups.length; 604 605 for(var h=0; h<dataGroups.length; h++){ 606 ctx.beginPath(); 607 608 var strokeWidth = linewidth - (o.barMargin*2); 609 ctx.lineWidth = strokeWidth; 610 var points = dataGroups[h].points; 611 var integer = 0; 612 for(var i=0; i<points.length; i++){ 613 // If the last value is zero, IE will go nuts and not draw anything, 614 // so don't try to draw zero values at all. 615 if (points[i].value != 0) { 616 var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2; 617 xVal += o.barGroupMargin*2; 618 619 ctx.moveTo(xVal, 0); 620 ctx.lineTo(xVal, Math.round(-points[i].value*yScale)); 621 } 622 integer+=barWidth; 623 } 624 ctx.strokeStyle = dataGroups[h].color; 625 ctx.stroke(); 626 ctx.closePath(); 627 } 628 629 } 630 }; 631 632 })(); 633 634 //create new canvas, set w&h attrs (not inline styles) 635 var canvasNode = document.createElement("canvas"); 636 var canvas = $(canvasNode) 637 .attr({ 638 'height': o.height, 639 'width': o.width 640 }); 641 642 //get title for chart 643 var title = o.title || self.find('caption').text(); 644 645 //create canvas wrapper div, set inline w&h, append 646 var canvasContain = (container || $('<div '+(o.chartId?'id="'+o.chartId+'" ':'')+'class="visualize '+o.chartClass+'" role="img" aria-label="Chart representing data from the table: '+ title +'" />')) 647 .height(o.height) 648 .width(o.width); 649 650 var scroller = $('<div class="visualize-scroller"></div>') 651 .appendTo(canvasContain) 652 .append(canvas); 653 654 //title/key container 655 if(o.appendTitle || o.appendKey){ 656 var infoContain = $('<div class="visualize-info"></div>') 657 .appendTo(canvasContain); 658 } 659 660 //append title 661 if(o.appendTitle){ 662 $('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain); 663 } 664 665 666 //append key 667 if(o.appendKey){ 668 var newKey = $('<ul class="visualize-key"></ul>'); 669 $.each(yAllLabels, function(i,label){ 670 $('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ label +'</span></li>') 671 .appendTo(newKey); 672 }); 673 newKey.appendTo(infoContain); 674 }; 675 676 // init interaction 677 if(o.interaction) { 678 // sets the canvas to track interaction 679 // IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly. 680 // Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove
681 var tracker = $('<div class="visualize-interaction-tracker"/>') 682 .css({ 683 'height': o.height + 'px', 684 'width': o.width + 'px', 685 'position':'relative', 686 'z-index': 200 687 }) 688 .insertAfter(canvas); 689 690 var triggerInteraction = function(overOut,data) { 691 var data = $.extend({ 692 canvasContain:canvasContain, 693 tableData:tableData 694 },data); 695 self.trigger('vizualize'+overOut,data); 696 }; 697 698 var over=false, last=false, started=false; 699 tracker.mousemove(function(e){ 700 var x,y,x1,y1,data,dist,i,current,selector,zLabel,elem,color,minDist,found,ev=e.originalEvent; 701 702 // get mouse position relative to the tracker/canvas 703 x = ev.layerX || ev.offsetX || 0; 704 y = ev.layerY || ev.offsetY || 0; 705 706 found = false; 707 minDist = started?30000:(o.type=='pie'?(Math.round(canvas.height()/2)-o.pieMargin)/3:o.lineWeight*4); 708 // iterate datagroups to find points with matching 709 $.each(charts[o.type].interactionPoints,function(i,current){ 710 x1 = current.canvasCords[0] + zeroLocX; 711 y1 = current.canvasCords[1] + (o.type=="pie"?0:zeroLocY); 712 dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) ); 713 if(dist < minDist) { 714 found = current; 715 minDist = dist; 716 } 717 }); 718 719 if(o.multiHover && found) { 720 x = found.canvasCords[0] + zeroLocX; 721 y = found.canvasCords[1] + (o.type=="pie"?0:zeroLocY); 722 found = [found]; 723 $.each(charts[o.type].interactionPoints,function(i,current){ 724 if(current == found[0]) {return;} 725 x1 = current.canvasCords[0] + zeroLocX; 726 y1 = current.canvasCords[1] + zeroLocY; 727 dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) ); 728 if(dist <= o.multiHover) { 729 found.push(current); 730 } 731 }); 732 } 733 // trigger over and out only when state changes, instead of on every mousemove 734 over = found; 735 if(over != last) { 736 if(over) { 737 if(last) { 738 triggerInteraction('Out',{point:last}); 739 } 740 triggerInteraction('Over',{point:over}); 741 last = over; 742 } 743 if(last && !over) { 744 triggerInteraction('Out',{point:last}); 745 last=false; 746 } 747 started=true; 748 } 749 }); 750 tracker.mouseleave(function(){ 751 triggerInteraction('Out',{ 752 point:last, 753 mouseOutGraph:true 754 }); 755 over = (last = false); 756 }); 757 } 758 759 //append new canvas to page 760 if(!container){canvasContain.insertAfter(this); } 761 if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); } 762 763 //set up the drawing board 764 var ctx = canvas[0].getContext('2d'); 765 766 // Scroll graphs 767 scroller.scrollLeft(o.width-scroller.width()); 768 769 // init plugins 770 $.each($.visualizePlugins,function(i,plugin){ 771 plugin.call(self,o,tableData); 772 }); 773 774 //create chart 775 charts[o.type].setup(); 776 777 if(!container){ 778 //add event for updating 779 self.bind('visualizeRefresh', function(){ 780 self.visualize(o, $(this).empty()); 781 }); 782 //add event for redraw 783 self.bind('visualizeRedraw', function(){ 784 charts[o.type].draw(); 785 }); 786 } 787 }).next(); //returns canvas(es) 788}; 789// create array for plugins. if you wish to make a plugin, 790// just push your init funcion into this array 791$.visualizePlugins = []; 792 793})(jQuery);
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.