Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit 97258ee1bda258b0bb22e693d3481e3e65c5d4dc 0 parents
@marclove marclove authored
132 README.markdown
@@ -0,0 +1,132 @@
+Accessible data visualization in HTML has always been tricky to achieve, particularly because elements such as images allow only the most basic features for providing textual information to non-visual users. A while back, we wrote an article describing a technique we came up with to use JavaScript to scrape data from an HTML table and generate charts using the HTML 5 Canvas element. The technique is particularly useful because the data for the visualization already exists in the page in structured tabular format, making it accessible to people who browse the web with a screen reader or other assistive technology.
+
+We've now rewritten and extended the code behind this technique and packaged it up as a new jQuery plugin called "visualize", which you can download below. The plugin provides a simple method for generating bar, line, area, and pie charts from an HTML table, and allows you to configure them in a variety of ways.
+
+First, a quick demo
+
+In the example below, we have an HTML table populated with sample data of a number of employees and their sales by store department. We've generated 4 charts from this table, which are shown below.
+
+View this example in a new window
+So how does it work?
+
+First, you'll need to create the table markup:
+
+<table >
+ <caption>2009 Individual Sales by Category</caption>
+ <thead>
+ <tr>
+ <td></td>
+ <th>food</th>
+ <th>auto</th>
+ <th>household</th>
+ <th>furniture</th>
+ <th>kitchen</th>
+ <th>bath</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>Mary</th>
+ <td>150</td>
+ <td>160</td>
+ <td>40</td>
+ <td>120</td>
+ <td>30</td>
+ <td>70</td>
+ </tr>
+ <tr>
+ <th>Tom</th>
+ <td>3</td>
+ <td>40</td>
+ <td>30</td>
+ <td>45</td>
+ <td>35</td>
+ <td>49</td>
+ </tr>
+ ...repetitive rows removed for brevity.
+ </tbody>
+</table>
+
+Note that we've used a caption element to summarize the table data. This will be used by the visualize plugin to create a title on your graph. We've also defined our table headings using th elements, letting the script know which cells it should use as the titles for a data set.
+
+Now that we have our HTML table, we can generate a chart. Just attach jQuery and our visualize plugin's JavaScript and CSS files to your page, and call the visualize() method on the table, like this:
+
+
+$('table').visualize();
+
+That's it! By default, the visualize plugin will generate the first bar chart shown above and append it to your page directly after the table.
+
+
+Working with a generated chart
+
+Finding the generated chart on the page
+
+Once you call the visualize() method on a table, the new chart element will be returned to the method, allowing you to continue your jQuery chain acting upon the chart instead of the table. Charts generated by this plugin are contained within a div element with a class of "visualize" as well as a class of the chart type, such as "visualize-pie". These classes make it easy to find your chart after it's generated, for additional presentation and behavioral modifications. Another nice way to do this is to store your generated chart in a variable, like this: var myChart = $('table').visualize();. Then you can simply reference myChart later on in your script to make any modifications to it, or to remove it from the page.
+
+Updating a chart
+
+Every chart generated by the visualize plugin has a custom event that can be used to refresh itself using its original settings, including which table it should pull data from. This is handy for dynamic pages with charts that can update frequently. In fact, we made use of this event when creating the editable table example above. To refresh an existing chart, simple trigger the visualizeRefresh event on the generated chart element, like this:
+
+$('.visualize').trigger('visualizeRefresh');
+
+Appending the chart to other areas of the page
+
+Since calling the visualize() method returns the new chart element, it's easy to immediately append the chart to another area of the page using jQuery's appendTo method. However, once you move the chart to another area in the DOM, you must trigger the visualizeRefresh method on it in order for it to display properly in Internet Explorer 6 and 7. The following code demonstrates appending the chart to the end of the page, and then triggering the visualizeRefresh method on it:
+
+$('table')
+ .visualize()
+ .appendTo('body')
+ .trigger('visualizeRefresh');
+
+Use CSS to Configure the Text
+
+The CSS file that accompanies the visualize plugin handles much of the presentation for the key, title, grid lines, and axis labels. So for instance, if you need to change the placement of the key, simply edit the CSS rules that apply to it (we customized the key in the pie chart example above using this same approach).
+
+Configuring Visualize with options
+
+The following options are available in this plugin:
+
+ * type: string. Accepts 'bar', 'area', 'pie', 'line'. Default: 'bar'.
+ * width: number. Width of chart. Defaults to table width
+ * height: number. Height of chart. Defaults to table height
+ * appendTitle: boolean. Add title to chart. Default: true.
+ * title: string. Title for chart. Defaults to text of table's Caption element.
+ * appendKey: boolean. Add the color key to the chart. Default: true.
+ * colors: array. Array items are hex values, used in order of appearance. Default: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744']
+ * textColors: array. Array items are hex values. Each item corresponds with colors array. null/undefined items will fall back to CSS text color. Default: [].
+ * parseDirection: string. Direction to parse the table data. Accepts 'x' and 'y'. Default: 'x'.
+ * pieMargin: number. Space around outer circle of Pie chart. Default: 20.
+ * pieLabelPos: string. Position of text labels in Pie chart. Accepts 'inside' and 'outside'. Default: 'inside'.
+ * lineWeight: number. Stroke weight for lines in line and area charts. Default: 4.
+ * barGroupMargin: number. Space around each group of bars in a bar chart. Default: 10.
+ * barMargin: number. Creates space around bars in bar chart (added to both sides of each bar). Default: 1
+
+To use the options, simply pass them as an argument to the visualize() method using object literal notation, just like most other jQuery plugins you're used to ( for example: visualize({ optionA: valueA, optionB: valueB});).
+
+Live Options Configuration Demo
+
+To experiment with these options, try out the following configuration demo:
+
+View this example in a new window
+
+Download the Code!
+
+You can download the scripts and a demo page in the following zip file: visualize.filamentgroup.zip. The plugin itself is licensed for free distribution with MIT and GPL licenses, just like jQuery itself.
+
+Browser Support
+
+We have tested this plugin in the following browsers: IE6, IE7, IE8, Firefox 2, Firefox 3.5, Safari 3 and 4, Opera 9.
+
+Note for Internet Explorer support
+
+This plugin uses the HTML 5 canvas element, which is not supported in an version of Internet Explorer at this time. Fortunately, Google maintains a library that translates canvas scripting into VML, allowing it to work in all versions of internet explorer. The script is included in the zip. To use it, just be sure to include the script in your page using a conditional comment, like this:
+
+<!--[if IE]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]-->
+
+Still to-do
+
+While this plugin is fairly feature-complete and works well across the major browsers, we would like to consider implementing the following features:
+
+ * Key text formatting: The ability to configure the text in the keys to show member title, total percentage, total amount, and more
+ * Interactivity: We may experiment with adding datapoint tooltips, and other ways to interact with the visualization.
+ * Support for data subsets: The ability to specify a subset of the table rows and columns for visualizing.
27 demopage.css
@@ -0,0 +1,27 @@
+body { font-size: 62.5%; font-family: Verdana, sans-serif; }
+/*demo styles*/
+table {width: 500px; height: 200px; border-collapse: collapse; margin-left: 30px; }
+table.accessHide { position: absolute; left: -999999px; }
+td, th {text-align: center; border: 1px solid #ddd; height: 1.5em; padding: 4px;}
+td.hover { color: orange; }
+td.input { padding: 0; }
+td input, td input:focus { border: 1px solid orange; outline: none; padding: 2px; margin: 1px; width: 20px; }
+caption {margin: 0 0 .5em; font-size: 1.3em; text-align: left; }
+.visualize { margin: 60px 0 0 30px; }
+
+.editableNote { background: #FEFBE2; border: 1px solid #8CC264; padding: 10px; margin: 1.5em 0; font-size: 1.2em; }
+
+/*sample alternate styling for info block on Pie Chart */
+.visualize-pie .visualize-info { top: 10px; border: 0; right: auto; left: 10px; padding: 0; background: none; }
+.visualize-pie ul.visualize-title { font-weight: bold; border: 0; }
+.visualize-pie ul.visualize-key li { float: none; }
+
+
+
+/*option configurator thingy*/
+.chartConfiguratorThingy { border: 1px solid #ddd; padding: 10px; overflow: auto; display: none; }
+.chartConfiguratorThingy h2 { font-size: 1.4em; color: #666; }
+fieldset { margin: 5px 5px 5px 0; width: 45%; float: left; }
+legend { font-size: 1.2em; }
+.dependencies { padding: 15px; }
+label { width: 90px; display: inline-block; }
35 excanvas.compiled.js
@@ -0,0 +1,35 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_||
+b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])},
+initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect();
+break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]=
+h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+
+1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b;
+var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};
+i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+
+0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b,
+a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b,
+a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length==
+5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=",
+this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src,
+'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="',
+!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius),
+" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_),
+z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+
+o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap),
+'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(),
+this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a,
+0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager=
+M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})();
75 index.html
@@ -0,0 +1,75 @@
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Demo page</title>
+ <link type="text/css" rel="stylesheet" href="visualize.jQuery.css"/>
+ <link type="text/css" rel="stylesheet" href="demopage.css"/>
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+ <!--[if IE]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]-->
+ <script type="text/javascript" src="visualize.jQuery.js"></script>
+ <script type="text/javascript">
+ $(function(){
+ //make some charts
+ $('table').visualize({type: 'pie', pieMargin: 10, title: '2009 Total Sales by Individual'});
+ $('table').visualize({type: 'line'});
+ $('table').visualize({type: 'area'});
+ $('table').visualize();
+ });
+ </script>
+ </head>
+ <body>
+
+ <table >
+ <caption>2009 Individual Sales by Category</caption>
+ <thead>
+ <tr>
+ <td></td>
+ <th>food</th>
+ <th>auto</th>
+ <th>household</th>
+ <th>furniture</th>
+ <th>kitchen</th>
+ <th>bath</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>Mary</th>
+ <td>190</td>
+ <td>160</td>
+ <td>40</td>
+ <td>120</td>
+ <td>30</td>
+ <td>70</td>
+ </tr>
+ <tr>
+ <th>Tom</th>
+ <td>3</td>
+ <td>40</td>
+ <td>30</td>
+ <td>45</td>
+ <td>35</td>
+ <td>49</td>
+ </tr>
+ <tr>
+ <th>Brad</th>
+ <td>10</td>
+ <td>180</td>
+ <td>10</td>
+ <td>85</td>
+ <td>25</td>
+ <td>79</td>
+ </tr>
+ <tr>
+ <th>Kate</th>
+ <td>40</td>
+ <td>80</td>
+ <td>90</td>
+ <td>25</td>
+ <td>15</td>
+ <td>119</td>
+ </tr>
+ </tbody>
+ </table>
+ </body>
+</html>
32 visualize.jQuery.css
@@ -0,0 +1,32 @@
+/*plugin styles*/
+.visualize { border: 1px solid #888; position: relative; background: #fafafa; }
+.visualize canvas { position: absolute; }
+.visualize ul,.visualize li { margin: 0; padding: 0;}
+
+/*table title, key elements*/
+.visualize .visualize-info { padding: 3px 5px; background: #fafafa; border: 1px solid #888; position: absolute; top: -20px; right: 10px; opacity: .8; }
+.visualize .visualize-title { display: block; color: #333; margin-bottom: 3px; font-size: 1.1em; }
+.visualize ul.visualize-key { list-style: none; }
+.visualize ul.visualize-key li { list-style: none; float: left; margin-right: 10px; padding-left: 10px; position: relative;}
+.visualize ul.visualize-key .visualize-key-color { width: 6px; height: 6px; left: 0; position: absolute; top: 50%; margin-top: -3px; }
+.visualize ul.visualize-key .visualize-key-label { color: #000; }
+
+/*pie labels*/
+.visualize-pie .visualize-labels { list-style: none; }
+.visualize-pie .visualize-label-pos, .visualize-pie .visualize-label { position: absolute; margin: 0; padding:0; }
+.visualize-pie .visualize-label { display: block; color: #fff; font-weight: bold; font-size: 1em; }
+.visualize-pie-outside .visualize-label { color: #000; font-weight: normal; }
+
+/*line,bar, area labels*/
+.visualize-labels-x,.visualize-labels-y { position: absolute; left: 0; top: 0; list-style: none; }
+.visualize-labels-x li, .visualize-labels-y li { position: absolute; bottom: 0; }
+.visualize-labels-x li span.label, .visualize-labels-y li span.label { position: absolute; color: #555; }
+.visualize-labels-x li span.line, .visualize-labels-y li span.line { position: absolute; border: 0 solid #ccc; }
+.visualize-labels-x li { height: 100%; }
+.visualize-labels-x li span.label { top: 100%; margin-top: 5px; }
+.visualize-labels-x li span.line { border-left-width: 1px; height: 100%; display: block; }
+.visualize-labels-x li span.line { border: 0;} /*hide vertical lines on area, line, bar*/
+.visualize-labels-y li { width: 100%; }
+.visualize-labels-y li span.label { right: 100%; margin-right: 5px; }
+.visualize-labels-y li span.line { border-top-width: 1px; width: 100%; }
+.visualize-bar .visualize-labels-x li span.label { width: 100%; text-align: center; }
438 visualize.jQuery.js
@@ -0,0 +1,438 @@
+/**
+ * --------------------------------------------------------------------
+ * jQuery-Plugin "visualize"
+ * by Scott Jehl, scott@filamentgroup.com
+ * http://www.filamentgroup.com
+ * Copyright (c) 2009 Filament Group
+ * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
+ *
+ * --------------------------------------------------------------------
+ */
+(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
+ 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
+ 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)
+ },options);
+
+ //reset width, height to numbers
+ o.width = parseInt(o.width,10);
+ o.height = parseInt(o.height,10);
+
+
+ var self = $(this);
+
+ //function to scrape data from html table
+ function scrapeTable(){
+ var colors = o.colors;
+ var textColors = o.textColors;
+ var tableData = {
+ members: function(){
+ var members = [];
+ if(o.parseDirection == 'x'){
+ self.find('tr:gt(0)').each(function(i){
+ members[i] = {};
+ members[i].points = [];
+ members[i].color = colors[i];
+ if(textColors[i]){ members[i].textColor = textColors[i]; }
+ $(this).find('td').each(function(){
+ members[i].points.push($(this).text()*1);
+ });
+ });
+ }
+ else {
+ var cols = self.find('tr:eq(1) td').size();
+ for(var i=0; i<cols; i++){
+ members[i] = {};
+ members[i].points = [];
+ members[i].color = colors[i];
+ if(textColors[i]){ members[i].textColor = textColors[i]; }
+ self.find('tr:gt(0)').each(function(){
+ members[i].points.push( $(this).find('td').eq(i).text()*1 );
+ });
+ };
+ }
+ return members;
+ },
+ allData: function(){
+ var allData = [];
+ $(this.members()).each(function(){
+ allData.push(this.points);
+ });
+ return allData;
+ },
+ dataSum: function(){
+ var dataSum = 0;
+ var allData = this.allData().join(',').split(',');
+ $(allData).each(function(){
+ dataSum += parseInt(this,10);
+ });
+ return dataSum
+ },
+ topValue: function(){
+ var topValue = 0;
+ var allData = this.allData().join(',').split(',');
+ $(allData).each(function(){
+ if(parseInt(this,10)>topValue) topValue = parseInt(this,10);
+ });
+ return topValue;
+ },
+ memberTotals: function(){
+ var memberTotals = [];
+ var members = this.members();
+ $(members).each(function(l){
+ var count = 0;
+ $(members[l].points).each(function(m){
+ count +=members[l].points[m];
+ });
+ memberTotals.push(count);
+ });
+ return memberTotals;
+ },
+ yTotals: function(){
+ var yTotals = [];
+ var members = this.members();
+ var loopLength = this.xLabels().length;
+ for(var i = 0; i<loopLength; i++){
+ yTotals[i] =[];
+ var thisTotal = 0;
+ $(members).each(function(l){
+ yTotals[i].push(this.points[i]);
+ });
+ yTotals[i].join(',').split(',');
+ $(yTotals[i]).each(function(){
+ thisTotal += parseInt(this);
+ });
+ yTotals[i] = thisTotal;
+
+ }
+ return yTotals;
+ },
+ topYtotal: function(){
+ var topYtotal = 0;
+ var yTotals = this.yTotals().join(',').split(',');
+ $(yTotals).each(function(){
+ if(parseInt(this,10)>topYtotal) topYtotal = parseInt(this,10);
+ });
+ return topYtotal;
+ },
+ xLabels: function(){
+ var xLabels = [];
+ if(o.parseDirection == 'x'){
+ self.find('tr:eq(0) th').each(function(){
+ xLabels.push($(this).html());
+ });
+ }
+ else{
+ self.find('tr:gt(0) th').each(function(){
+ xLabels.push($(this).html());
+ });
+ }
+ return xLabels;
+ },
+ yLabels: function(){
+ var yLabels = [];
+ var chartHeight = o.height;
+ var numLabels = chartHeight / 30;
+ var loopInterval = Math.round(this.topValue() / numLabels);
+
+ for(var j=0; j<=numLabels; j++){
+ yLabels.push(j*loopInterval);
+ }
+ if(yLabels[numLabels] != this.topValue()) {
+ yLabels.pop();
+ yLabels.push(this.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 / 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 = members[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 labeltext = $('<span class="visualize-label">' + Math.round(fraction*100) + '%</span>')
+ .css(leftRight, 0)
+ .css(topBottom, 0);
+ 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(members[i].textColor){ labeltext.css('color', members[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() / topValue;
+ 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,canvas.height());
+ //iterate and draw
+ $.each(members,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() / topValue;
+ 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,canvas.height());
+ //iterate and draw
+ for(var h=0; h<members.length; h++){
+ ctx.beginPath();
+ var linewidth = (xInterval-o.barGroupMargin*2) / members.length; //removed +1
+ var strokeWidth = linewidth - (o.barMargin*2);
+ ctx.lineWidth = strokeWidth;
+ var points = members[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 = members[h].color;
+ ctx.stroke();
+ ctx.closePath();
+ }
+ }
+ };
+
+ //create new canvas, set w&h attrs (not inline styles)
+ var canvas = $('<canvas/>')
+ .attr('height',o.height)
+ .attr('width',o.width)
+ .css({width: o.width, height: o.height});
+
+
+ //create canvas wrapper div, set inline w&h, append
+ var canvasContain = (container || $('<div class="visualize" role="presentation" />'))
+ .height(o.height)
+ .width(o.width)
+ .append(canvas);
+
+ //scrape table (this should be cleaned up into an obj)
+ var tableData = scrapeTable();
+ var members = tableData.members();
+ var allData = tableData.allData();
+ var dataSum = tableData.dataSum();
+ var topValue = tableData.topValue();
+ var memberTotals = tableData.memberTotals();
+ 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){
+ var title = o.title || self.find('caption').text();
+ $('<div class="visualize-title">'+ title +'</div>')
+ .appendTo(infoContain);
+ }
+
+ //append key
+ if(o.appendKey){
+ var newKey = $('<ul class="visualize-key"></ul>');
+ var selector = (o.parseDirection == 'x') ? 'tr:gt(0) th' : 'tr:eq(0) th' ;
+ self.find(selector).each(function(i){
+ $('<li><span class="visualize-key-color" style="background: '+members[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($.browser.msie){ 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);
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.