| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- /*
- * --------------------------------------------------------------------
- * jQuery inputToButton plugin
- * Author: Scott Jehl, scott@filamentgroup.com
- * Copyright (c) 2009 Filament Group
- * licensed under MIT (filamentgroup.com/examples/mit-license.txt)
- * --------------------------------------------------------------------
- */
- (function($) {
- $.fn.visualize = function(options, container){
- return $(this).each(function(){
- //configuration
- var o = $.extend({
- type: 'bar', //also available: area, pie, line
- width: $(this).width(), //height of canvas - defaults to table height
- height: $(this).height(), //height of canvas - defaults to table height
- appendTitle: true, //table caption text is added to chart
- title: null, //grabs from table caption if null
- appendKey: true, //color key is added to chart
- rowFilter: ' ',
- colFilter: ' ',
- colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'],
- textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS
- parseDirection: 'x', //which direction to parse the table data
- pieMargin: 20, //pie charts only - spacing around pie
- pieLabelsAsPercent: true,
- pieLabelPos: 'inside',
- lineWeight: 4, //for line and area - stroke weight
- barGroupMargin: 10,
- barMargin: 1, //space around bars in bar chart (added to both sides of bar)
- yLabelInterval: 30 //distance between y labels
- },options);
-
- //reset width, height to numbers
- o.width = parseFloat(o.width);
- o.height = parseFloat(o.height);
-
-
- var self = $(this);
-
- //function to scrape data from html table
- function scrapeTable(){
- var colors = o.colors;
- var textColors = o.textColors;
- var tableData = {
- dataGroups: function(){
- var dataGroups = [];
- if(o.parseDirection == 'x'){
- self.find('tr:gt(0)').filter(o.rowFilter).each(function(i){
- dataGroups[i] = {};
- dataGroups[i].points = [];
- dataGroups[i].color = colors[i];
- if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
- $(this).find('td').filter(o.colFilter).each(function(){
- dataGroups[i].points.push( parseFloat($(this).text()) );
- });
- });
- }
- else {
- var cols = self.find('tr:eq(1) td').filter(o.colFilter).size();
- for(var i=0; i<cols; i++){
- dataGroups[i] = {};
- dataGroups[i].points = [];
- dataGroups[i].color = colors[i];
- if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
- self.find('tr:gt(0)').filter(o.rowFilter).each(function(){
- dataGroups[i].points.push( $(this).find('td').filter(o.colFilter).eq(i).text()*1 );
- });
- };
- }
- return dataGroups;
- },
- allData: function(){
- var allData = [];
- $(this.dataGroups()).each(function(){
- allData.push(this.points);
- });
- return allData;
- },
- dataSum: function(){
- var dataSum = 0;
- var allData = this.allData().join(',').split(',');
- $(allData).each(function(){
- dataSum += parseFloat(this);
- });
- return dataSum
- },
- topValue: function(){
- var topValue = 0;
- var allData = this.allData().join(',').split(',');
- $(allData).each(function(){
- if(parseFloat(this,10)>topValue) topValue = parseFloat(this);
- });
- return topValue;
- },
- bottomValue: function(){
- var bottomValue = 0;
- var allData = this.allData().join(',').split(',');
- $(allData).each(function(){
- if(this<bottomValue) bottomValue = parseFloat(this);
- });
- return bottomValue;
- },
- memberTotals: function(){
- var memberTotals = [];
- var dataGroups = this.dataGroups();
- $(dataGroups).each(function(l){
- var count = 0;
- $(dataGroups[l].points).each(function(m){
- count +=dataGroups[l].points[m];
- });
- memberTotals.push(count);
- });
- return memberTotals;
- },
- yTotals: function(){
- var yTotals = [];
- var dataGroups = this.dataGroups();
- var loopLength = this.xLabels().length;
- for(var i = 0; i<loopLength; i++){
- yTotals[i] =[];
- var thisTotal = 0;
- $(dataGroups).each(function(l){
- yTotals[i].push(this.points[i]);
- });
- yTotals[i].join(',').split(',');
- $(yTotals[i]).each(function(){
- thisTotal += parseFloat(this);
- });
- yTotals[i] = thisTotal;
-
- }
- return yTotals;
- },
- topYtotal: function(){
- var topYtotal = 0;
- var yTotals = this.yTotals().join(',').split(',');
- $(yTotals).each(function(){
- if(parseFloat(this,10)>topYtotal) topYtotal = parseFloat(this);
- });
- return topYtotal;
- },
- totalYRange: function(){
- return this.topValue() - this.bottomValue();
- },
- xLabels: function(){
- var xLabels = [];
- if(o.parseDirection == 'x'){
- self.find('tr:eq(0) th').filter(o.colFilter).each(function(){
- xLabels.push($(this).html());
- });
- }
- else {
- self.find('tr:gt(0) th').filter(o.rowFilter).each(function(){
- xLabels.push($(this).html());
- });
- }
- return xLabels;
- },
- yLabels: function(){
- var yLabels = [];
- yLabels.push(bottomValue);
- var numLabels = Math.round(o.height / o.yLabelInterval);
- var loopInterval = Math.ceil(totalYRange / numLabels) || 1;
- while( yLabels[yLabels.length-1] < topValue - loopInterval){
- yLabels.push(yLabels[yLabels.length-1] + loopInterval);
- }
- yLabels.push(topValue);
- return yLabels;
- }
- };
-
- return tableData;
- };
-
-
- //function to create a chart
- var createChart = {
- pie: function(){
-
- canvasContain.addClass('visualize-pie');
-
- if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); }
-
- var centerx = Math.round(canvas.width()/2);
- var centery = Math.round(canvas.height()/2);
- var radius = centery - o.pieMargin;
- var counter = 0.0;
- var toRad = function(integer){ return (Math.PI/180)*integer; };
- var labels = $('<ul class="visualize-labels"></ul>')
- .insertAfter(canvas);
- //draw the pie pieces
- $.each(memberTotals, function(i){
- var fraction = (this <= 0 || isNaN(this))? 0 : this / dataSum;
- ctx.beginPath();
- ctx.moveTo(centerx, centery);
- ctx.arc(centerx, centery, radius,
- counter * Math.PI * 2 - Math.PI * 0.5,
- (counter + fraction) * Math.PI * 2 - Math.PI * 0.5,
- false);
- ctx.lineTo(centerx, centery);
- ctx.closePath();
- ctx.fillStyle = dataGroups[i].color;
- ctx.fill();
- // draw labels
- var sliceMiddle = (counter + fraction/2);
- var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius + radius / 5;
- var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance));
- var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance));
- var leftRight = (labelx > centerx) ? 'right' : 'left';
- var topBottom = (labely > centery) ? 'bottom' : 'top';
- var percentage = parseFloat((fraction*100).toFixed(2));
- if(percentage){
- var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : this;
- var labeltext = $('<span class="visualize-label">' + labelval +'</span>')
- .css(leftRight, 0)
- .css(topBottom, 0);
- if(labeltext)
- var label = $('<li class="visualize-label-pos"></li>')
- .appendTo(labels)
- .css({left: labelx, top: labely})
- .append(labeltext);
- labeltext
- .css('font-size', radius / 8)
- .css('margin-'+leftRight, -labeltext.width()/2)
- .css('margin-'+topBottom, -labeltext.outerHeight()/2);
-
- if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); }
- }
- counter+=fraction;
- });
- },
-
- line: function(area){
-
- if(area){ canvasContain.addClass('visualize-area'); }
- else{ canvasContain.addClass('visualize-line'); }
-
- //write X labels
- var xInterval = canvas.width() / (xLabels.length -1);
- var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
- .width(canvas.width())
- .height(canvas.height())
- .insertBefore(canvas);
- $.each(xLabels, function(i){
- var thisLi = $('<li><span>'+this+'</span></li>')
- .prepend('<span class="line" />')
- .css('left', xInterval * i)
- .appendTo(xlabelsUL);
- var label = thisLi.find('span:not(.line)');
- var leftOffset = label.width()/-2;
- if(i == 0){ leftOffset = 0; }
- else if(i== xLabels.length-1){ leftOffset = -label.width(); }
- label
- .css('margin-left', leftOffset)
- .addClass('label');
- });
- //write Y labels
- var yScale = canvas.height() / totalYRange;
- var liBottom = canvas.height() / (yLabels.length-1);
- var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
- .width(canvas.width())
- .height(canvas.height())
- .insertBefore(canvas);
-
- $.each(yLabels, function(i){
- var thisLi = $('<li><span>'+this+'</span></li>')
- .prepend('<span class="line" />')
- .css('bottom',liBottom*i)
- .prependTo(ylabelsUL);
- var label = thisLi.find('span:not(.line)');
- var topOffset = label.height()/-2;
- if(i == 0){ topOffset = -label.height(); }
- else if(i== yLabels.length-1){ topOffset = 0; }
- label
- .css('margin-top', topOffset)
- .addClass('label');
- });
- //start from the bottom left
- ctx.translate(0,zeroLoc);
- //iterate and draw
- $.each(dataGroups,function(h){
- ctx.beginPath();
- ctx.lineWidth = o.lineWeight;
- ctx.lineJoin = 'round';
- var points = this.points;
- var integer = 0;
- ctx.moveTo(0,-(points[0]*yScale));
- $.each(points, function(){
- ctx.lineTo(integer,-(this*yScale));
- integer+=xInterval;
- });
- ctx.strokeStyle = this.color;
- ctx.stroke();
- if(area){
- ctx.lineTo(integer,0);
- ctx.lineTo(0,0);
- ctx.closePath();
- ctx.fillStyle = this.color;
- ctx.globalAlpha = .3;
- ctx.fill();
- ctx.globalAlpha = 1.0;
- }
- else {ctx.closePath();}
- });
- },
-
- area: function(){
- createChart.line(true);
- },
-
- bar: function(){
-
- canvasContain.addClass('visualize-bar');
-
- //write X labels
- var xInterval = canvas.width() / (xLabels.length);
- var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
- .width(canvas.width())
- .height(canvas.height())
- .insertBefore(canvas);
- $.each(xLabels, function(i){
- var thisLi = $('<li><span class="label">'+this+'</span></li>')
- .prepend('<span class="line" />')
- .css('left', xInterval * i)
- .width(xInterval)
- .appendTo(xlabelsUL);
- var label = thisLi.find('span.label');
- label.addClass('label');
- });
- //write Y labels
- var yScale = canvas.height() / totalYRange;
- var liBottom = canvas.height() / (yLabels.length-1);
- var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
- .width(canvas.width())
- .height(canvas.height())
- .insertBefore(canvas);
- $.each(yLabels, function(i){
- var thisLi = $('<li><span>'+this+'</span></li>')
- .prepend('<span class="line" />')
- .css('bottom',liBottom*i)
- .prependTo(ylabelsUL);
- var label = thisLi.find('span:not(.line)');
- var topOffset = label.height()/-2;
- if(i == 0){ topOffset = -label.height(); }
- else if(i== yLabels.length-1){ topOffset = 0; }
- label
- .css('margin-top', topOffset)
- .addClass('label');
- });
- //start from the bottom left
- ctx.translate(0,zeroLoc);
- //iterate and draw
- for(var h=0; h<dataGroups.length; h++){
- ctx.beginPath();
- var linewidth = (xInterval-o.barGroupMargin*2) / dataGroups.length; //removed +1
- var strokeWidth = linewidth - (o.barMargin*2);
- ctx.lineWidth = strokeWidth;
- var points = dataGroups[h].points;
- var integer = 0;
- for(var i=0; i<points.length; i++){
- var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2;
- xVal += o.barGroupMargin*2;
-
- ctx.moveTo(xVal, 0);
- ctx.lineTo(xVal, Math.round(-points[i]*yScale));
- integer+=xInterval;
- }
- ctx.strokeStyle = dataGroups[h].color;
- ctx.stroke();
- ctx.closePath();
- }
- }
- };
-
- //create new canvas, set w&h attrs (not inline styles)
- var canvasNode = document.createElement("canvas");
- canvasNode.setAttribute('height',o.height);
- canvasNode.setAttribute('width',o.width);
- var canvas = $(canvasNode);
-
- //get title for chart
- var title = o.title || self.find('caption').text();
-
- //create canvas wrapper div, set inline w&h, append
- var canvasContain = (container || $('<div class="visualize" role="img" aria-label="Chart representing data from the table: '+ title +'" />'))
- .height(o.height)
- .width(o.width)
- .append(canvas);
- //scrape table (this should be cleaned up into an obj)
- var tableData = scrapeTable();
- var dataGroups = tableData.dataGroups();
- var allData = tableData.allData();
- var dataSum = tableData.dataSum();
- var topValue = tableData.topValue();
- var bottomValue = tableData.bottomValue();
- var memberTotals = tableData.memberTotals();
- var totalYRange = tableData.totalYRange();
- var zeroLoc = o.height * (topValue/totalYRange);
- var xLabels = tableData.xLabels();
- var yLabels = tableData.yLabels();
-
- //title/key container
- if(o.appendTitle || o.appendKey){
- var infoContain = $('<div class="visualize-info"></div>')
- .appendTo(canvasContain);
- }
-
- //append title
- if(o.appendTitle){
- $('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain);
- }
-
-
- //append key
- if(o.appendKey){
- var newKey = $('<ul class="visualize-key"></ul>');
- var selector;
- if(o.parseDirection == 'x'){
- selector = self.find('tr:gt(0) th').filter(o.rowFilter);
- }
- else{
- selector = self.find('tr:eq(0) th').filter(o.colFilter);
- }
-
- selector.each(function(i){
- $('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ $(this).text() +'</span></li>')
- .appendTo(newKey);
- });
- newKey.appendTo(infoContain);
- };
-
- //append new canvas to page
-
- if(!container){canvasContain.insertAfter(this); }
- if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); }
-
- //set up the drawing board
- var ctx = canvas[0].getContext('2d');
-
- //create chart
- createChart[o.type]();
-
- //clean up some doubled lines that sit on top of canvas borders (done via JS due to IE)
- $('.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');
- if(!container){
- //add event for updating
- canvasContain.bind('visualizeRefresh', function(){
- self.visualize(o, $(this).empty());
- });
- }
- }).next(); //returns canvas(es)
- };
- })(jQuery);
|