Created
June 1, 2013 03:49
-
-
Save dannycochran/5689222 to your computer and use it in GitHub Desktop.
Revisions
-
dannycochran created this gist
Jun 1, 2013 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,6 @@ # Twitter Word River A d3.js visualization showing trending Twitter words during natural catastrophes by distance from the epicenter and over time. Built on top of bunkat's Swimlane Chart: http://bl.ocks.org/1962173 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,565 @@ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>A River of Twitter Data</title> <script src="http://d3js.org/d3.v2.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> <script type="text/javascript" src="http://f.cl.ly/items/2U0A2k0d1O2e0F2Y143F/jquery-ui.js"></script> <script type="text/javascript" src="./tipsy.js"></script> <link rel="stylesheet" href="./style.css" type="text/css" /> <link rel="stylesheet" href="./tipsy.css" type="text/css" /> <link rel="stylesheet" href="http://code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css" /> </head> <body> <div> <h1> <img src="http://f.cl.ly/items/3S462I0S3v1M2F3d1L2V/twitter-bird-white-on-blue1.png"/> Activity, Hurricane Sandy <!-- play & pause buttons, hosted externally because Stanford doesn't seem to like these files on cgi-bin --> <span><img class='playbutton' type="button" value="Play" id="play" src="http://f.cl.ly/items/3O2M2V2w2a0j0s1I2I32/Play.png"/></span> <span><img class='playbutton' type="button" value="Stop" id="stop" style="display:none" src="http://f.cl.ly/items/3v452T2g0i0i1V1C113O/pause.png"/></span> </h1> </div> <script type="text/javascript"> // distance bins var distanceBins = [["0-50 Miles","Southern New Jersey"],["51-110 Miles","Includes Manhattan"],["111-200 Miles","Includes Washington D.C."],["201-500 Miles","Entire East Coast"],["501-1000 Miles","Midwest Region (including Chicago)"],["1001-3000 Miles","Continental U.S."],["≥ 3000 Miles","Rest of the World"]]; // for universal access, switchData contains the whole json file var jsonData; var jsonDataRT; var jsonDataFilter; var jsonDataRTFilter; var switchData; var checkData = 0; // these control the user's potential selection range for brushing, too large a difference yields a scrunched graph var max_extent_width = 10 , min_extent_width = 5; // min/max values of d.positivity, d.freqBin, and d.objectivity var positivityDomain = [0.0163966168908, 0.0418471720818] , frequencyDomain = [1,4] , objectivityDomain = [0.923212665835, 0.965169775079]; // vars for decreasing blueness (by increasing RG values) of heat maps var redOffset = 0; var greenOffset = 0; d3.json("./sandyData.json", function (json) { jsonData = json; jsonDataRT = json; switchData = json; jsonDataFilter = switchData.filter(function(d) {return d.isRT == 0}); jsonDataRTFilter = switchData.filter(function(d) {return d.isRT == 1}); switchData = jsonDataFilter; var margin = {top: 20, right: 15, bottom: 15, left: 105} , width = 960 - margin.left - margin.right , height = 500 - margin.top - margin.bottom , miniHeight = distanceBins.length * 12 + 50 , mainHeight = height - miniHeight - 50; x = d3.scale.linear() .domain([0,164]) .range([1, width]); x1 = d3.scale.linear().range([1, width]); var ext = d3.extent(distanceBins, function(d,i) { return i; }); y1 = d3.scale.linear().domain([ext[0], ext[1] + 1]).range([0, mainHeight]); y2 = d3.scale.linear().domain([ext[0], ext[1] + 1]).range([0, miniHeight]); //heat maps for main and mini graph (sets blue (RG) B value) colorScale = d3.scale.quantize() .domain(positivityDomain) .range([0,75,150,225]); // color will be arranged into four quartiles with quantize // font size scale from 12 to 20 wordSizeScale = d3.scale.quantize() .domain(frequencyDomain) .range([12,14,16,18]); // resizes fonts if user expands brush selection extentSizeScale = d3.scale.linear() .domain([min_extent_width,max_extent_width]) .range([2,4]); // determines if bins are emotional enough to be italic typeFaceScale = d3.scale.linear() .domain(objectivityDomain) .range([0,3]); var chart = d3.select('body') .append('svg:svg') .attr('width', width + margin.right + margin.left+105) .attr('height', height + margin.top + margin.bottom) .attr('class', 'chart'); chart.append('defs').append('clipPath') .attr('id', 'clip') .append('rect') .attr('width', width) .attr('height', mainHeight); main = chart.append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .attr('width', width) .attr('height', mainHeight) .attr('class', 'main'); mini = chart.append('g') .attr('transform', 'translate(' + margin.left + ',' + (mainHeight + 60) + ')') .attr('width', width) .attr('height', miniHeight) .attr('class', 'mini'); // draw the distanceBins for the main chart main.append('g').selectAll('.laneLines') .data(distanceBins) .enter().append('line') .attr('x1', 0) .attr('y1', function(d,i) { return d3.round(y1(i)) + 1; }) .attr('x2', width) .attr('y2', function(d,i) { return d3.round(y1(i)) + 1; }) .attr('stroke', function(d,i) { return 'lightgray' }); main.append('g').selectAll('.laneText') .data(distanceBins) .enter().append('text') .text(function(d) { return d[0]; }) .attr('x',-5) .attr('y', function(d,i) { return y1(i + .5); }) .attr('dy', '0.5ex') .attr('text-anchor', 'end') .attr('class', 'laneText') .attr('id', 'laneHover') .style('font-family', 'Rockwell') .on('mouseover',function() { $('svg #laneHover').tipsy({ gravity: 'nw', html: true, title: function() { var d = this.__data__; return d[1]; } }) }); // x-axis label main.append('text') .attr('class', 'x_label') .attr('id', 'x_label') .attr('text-anchor', 'end') .attr('x', width+70) .attr('y', mainHeight+5) .style('font-family', 'Rockwell') .text('hours from'); main.append('text') .attr('class', 'x_label') .attr('id', 'x_label') .attr('text-anchor', 'end') .attr('x', width+70) .attr('y', mainHeight + 19) .style('font-family', 'Rockwell') .text('landfall'); // sloppy legend - how does one wrap SVG text? main.append('svg:text') .attr('x', width+7) .attr('y', 15) .style('font-family', 'Rockwell') .style('font-size',12) .text('Lighter shades of'); main.append('svg:text') .attr('x', width+7) .attr('y', 30) .style('font-family', 'Rockwell') .style('font-size',12) .text('blue correspond to'); main.append('svg:text') .attr('x', width+7) .attr('y', 45) .style('font-family', 'Rockwell') .style('font-size',12) .text('higher positivity.'); main.append('svg:text') .attr('x', width+7) .attr('y', 60) .style('font-family', 'Rockwell') .style('font-size',12) .text('(Sentiwordnet'); main.append('svg:text') .attr('x', width+7) .attr('y', 75) .style('font-family', 'Rockwell') .style('font-size',12) .text('sentiment score)'); // toggle tweets on and off main.append('rect') .attr('id','toggle') .attr('x', width+7) .attr('y', 88) .attr('width',90) .attr('height',20) .style('fill','#4099FF') .on('mouseup', switchAllData) .attr('rx',5) .attr('ry',5); main.append('svg:text') .attr('id', 'retweet-toggle') .attr('x', width+15) .attr('y', 102) .text('Retweets: Off') // Toggles between on and off .style('font-family', 'Rockwell') .style('fill','white') .style('font-size',12) .style('pointer-events','none'); // draw the distanceBins for the mini chart mini.append('g').selectAll('.laneLines') .data(distanceBins) .enter().append('line') .attr('x1', 0) .attr('y1', function(d,i) { return d3.round(y2(i)) + 0.5; }) .attr('x2', width) .attr('y2', function(d,i) { return d3.round(y2(i)) + 0.5; }) .attr('stroke', function(d) { return d === '' ? 'white' : 'lightgray' }); mini.append('g').selectAll('.laneText') .data(distanceBins) .enter().append('svg:text') .text(function(d) { return d[0]; }) .attr('x', -10) .attr('y', function(d,i) { return y2(i+ 0.5); }) .attr('dy', '0.5ex') .attr('text-anchor', 'end') .attr('class', 'laneText'); // draw the x-axes xDateAxis = d3.svg.axis() .scale(x1) .ticks(20) .tickSize(15, 0, 0); x1DateAxis = d3.svg.axis() .scale(x) .ticks(20) .tickSize(15, 0, 0); main.append('g') .attr('transform', 'translate(0,' + mainHeight + ')') .attr('class', 'main axis') .call(xDateAxis); mini.append('g') .attr('transform', 'translate(0,125)') .attr('class', 'axis') .call(x1DateAxis) .selectAll('text') .attr('dx', 9) .attr('dy', 12); // declare the main rectangles to be amended in display() itemRects = main.append('g') .attr('clip-path', 'url(#clip)'); // draw mini graph mini.append('g').selectAll('miniItems') .data(switchData) .enter().append('rect') .attr('class', function(d) { return 'miniItem '; }) .attr("x",function(d,i) {return x(d.time_bin)}) .attr("y",function(d) {return y2(d.dist_bin+.08)}) .attr("width",x(2)-x(1)) .attr("height",y2(1)*.5) .style('fill', function(d) { var b = 255-(Math.round(colorScale(d.positivity))); return "rgb(" + redOffset + "," + greenOffset + "," + b + ")"; }); // invisible rect for when brush is moved by click instead of drag mini.append('rect') .attr('pointer-events', 'painted') .attr('width', width) .attr('height', miniHeight) .attr('visibility', 'hidden') .on('mouseup', moveBrush); // draw the selection area brush = d3.svg.brush() .x(x) .extent([0,5]) .on("brush", display) mini.append('g') .attr('class', 'x brush') .call(brush) .selectAll('rect') .attr('y', 1) .attr('id','brushHover') .attr('height', miniHeight - 1); mini.selectAll('rect.background').remove(); display(); }); // check to see which dataset is currently displayed, and if button is clicked, switch dataset function switchAllData() { if (checkData == 0) { main.select('#retweet-toggle') .text('Retweets: On'); main.select('#toggle') .transition().duration(500) .style('fill','#009900'); checkData = 1; switchData = jsonDataRTFilter; } else { main.select('#retweet-toggle') .text('Retweets: Off'); main.select('#toggle') .transition().duration(500) .style('fill','#4099FF'); checkData = 0; switchData = jsonDataFilter; } display(); } function display () { var rects, labels , minExtent = brush.extent()[0] , maxExtent = brush.extent()[1]; var extentWidth = maxExtent - minExtent; if (extentWidth > max_extent_width) maxExtent = minExtent + max_extent_width; if (extentWidth < min_extent_width) maxExtent = minExtent + min_extent_width; var visItems = switchData.filter(function (d) {return d.time_bin <= maxExtent+1 && d.time_bin >= minExtent-1}); mini.select('.brush').call(brush.extent([minExtent, maxExtent])); x1.domain([minExtent, maxExtent]); if ((maxExtent - minExtent) >= 10) { xDateAxis.ticks(10); } else { xDateAxis.ticks(5); } // update the axis main.select('.main.axis').call(xDateAxis) .selectAll('text') .attr('dx', 5) .attr('dy', 12); // upate the item rects rects = itemRects.selectAll('rect') .data(switchData, function (d,i) { return i; }) .attr('x', function(d) { return x1(d.time_bin); }) .attr('y', function(d) { return y1(d.dist_bin) + .1 * y1(1) + 0.5; }) .attr('width', 165); rects.enter().append('svg:rect') .attr('x', function(d) { return x1(d.time_bin); }) .attr('y', function(d) { return y1(d.dist_bin) + .1 * y1(1) + 0.5; }) .attr('width', 165) .attr('class','borderMe') .attr('height', function(d) { return .8 * y1(1); }) .style('fill', function(d) { var b = 255-(Math.round(colorScale(d.positivity))); return "rgb(" + redOffset + "," + greenOffset + "," + b + ")"; }); rects.exit().remove(); // update the item labels labels = itemRects.selectAll('text') .data(visItems, function (d) { return d.word; }) .text(function (d) { return d.word; }) .attr('x', function(d) { return Math.round(x1(d.time_bin+0.05)); }) .attr('y', function(d) { return y1(d.dist_bin) + .6 * y1(1)}) .attr('id','hover') .style('font-size', 14); labels.enter().append('svg:text') .text(function (d) { return d.word; }) .attr('x', function(d) { return Math.round(x1(d.time_bin+0.05)); }) .attr('y', function(d) { return y1(d.dist_bin) + .6 * y1(1) }) .attr('text-anchor', 'start') .attr('class', 'itemLabel') .attr('id','hover') .style('font-size', 14) .style('font-family', 'Arial') .style('fill','white') .on('mouseup',function(d) { var selectedWord = d.word; highlightWords(selectedWord); }); labels.order(); labels.exit().remove(); function highlightWords (a) { mini.selectAll('miniItems') .data(switchData) .enter().append('rect') .attr('class', function(d) { return 'miniItem '; }) .attr("x",function(d,i) {return x(d.time_bin)}) .attr("y",function(d) {return y2(d.dist_bin+.08)}) .attr("width",x(2)-x(1)) .attr("height",y2(1)*.5) .style('fill', function(d) { var b = Math.round(colorScale(d.positivity)); if (d.word == a) { return "rgb(0,200,0)"; } else { var b = 255-(Math.round(colorScale(d.positivity))); return "rgb(" + redOffset + "," + greenOffset + "," + b + ")"; } }) rects = itemRects.selectAll('rect') .data(switchData) .transition().duration(1000) .style('fill', function(d) { var b = 255-(Math.round(colorScale(d.positivity))); if (d.word == a) { return "rgb(" + redOffset + ",200," + greenOffset + ")"; } }) .transition().delay(2000).style('fill', function(d) { var b = 255-(Math.round(colorScale(d.positivity))); return "rgb(" + redOffset + "," + greenOffset + "," + b + ")"; }); } // tipsy hover states by element ID $('svg #hover').tipsy({ gravity: 'w', html: true, title: function() { var d = this.__data__; return d.sample_tweet; } }); $('svg #x_label').tipsy({ gravity: 'ne', html: true, title: function() { return "0 Hour is LandFall"; } }) $('svg #typeFace').tipsy({ gravity: 'ne', html: true, title: function() { return "<span style='font-family: Arial; font-style: italic'>Italic is highly emotional,</span>" + "<br>" + "<span style='font-family: Arial'>No styling is a normal level of emotion.</span>" + "<br>" + "<span style='font-family: Arial Black; font-weight:bold'>Bold is removed of emotion.</span>"; } }) $('svg #color').tipsy({ gravity: 'ne', html: true, title: function() { return "Lighter shades correlate to higher positivity. (Sentiwordnet sentiment score)"; } }) $('svg #frequency').tipsy({ gravity: 'ne', html: true, title: function() { return "Larger fonts correlate to higher relative word frequency."; } }) $('svg #brushHover').tipsy({ gravity: 'sw', html: true, title: function() { return "Drag or expand me to scroll through data."; } }) $('svg #toggle').tipsy({ gravity: 'ne', html: true, fontSize: 10, title: function() { return "Click here to add or remove retweets from the data."; } }) $('.tipsy:last').remove(); }; // for clicking brush to a location instead of dragging function moveBrush () { var origin = d3.mouse(this) , point = x.invert(origin[0]) , halfExtent = (brush.extent()[1] - brush.extent()[0]) / 2 , start = point - halfExtent , end = point + halfExtent; brush.extent([start,end]); display(); } // Set up auto play parameters var timer = null, duration = 100, extentIncrement = 0.05, extentMax = 164; // Define behavior of the Play button $('#play').click(function() { var extent = brush.extent(); if (extent[1] >= extentMax) { extent[1] -= extent[0]; extent[0] = 0; brush.extent(extent); } display(); if (timer) { clearInterval(timer); } timer = setInterval(function() { var extent = brush.extent(); if (extent[1] <= extentMax) { extent[0] += extentIncrement; extent[1] += extentIncrement; display(); brush.extent(extent); } else { $('#stop').click(); } }, duration); $(this).hide(); $('#stop').show(); }); $('#stop').click(function() { if (timer) { clearInterval(timer); } $(this).hide(); $('#play').show(); }); </script> </body> </html> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,87 @@ svg { margin-top: -30px; } div h1 { padding: 15px 20px 20px; margin-top:15px; margin-left:105px; background: #4099FF; color: white; font-size: 56px; max-width:800px; text-align: right; font-family: Rockwell, "Courier New", Courier, Georgia, Times, "Times New Roman", serif; } div img { float:left; margin-top: 5px; } .chart { shape-rendering: crispEdges; } .mini text { font: 9px sans-serif; } .main text { font-family: Rockwell, "Courier New", Courier, Georgia, Times, "Times New Roman", serif; font: 12px sans-serif; } .month text { text-anchor: start; } .todayLine { stroke: blue; stroke-width: 1.5; } .axis line, .axis path { stroke: black; } .axis text { font-family: Rockwell; } .miniItem { stroke-width: 6; } .future { stroke: gray; fill: #ddd; } .past { stroke: green; fill: lightgreen; } .brush .extent { stroke: gray; fill: blue; fill-opacity: .165; } .laneText { font-family: Rockwell, "Courier New", Courier, Georgia, Times, "Times New Roman", serif; } .borderMe { border-style:solid; border-right:thick double #ff0000; } span img { float:right; margin-right: -130px; margin-top: -15px; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,25 @@ .tipsy { font-size: 14px; position: absolute; padding: 5px; z-index: 100000; } .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: left; font-family: Rockwell; } /* Rounded corners */ .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } /* Uncomment for shadow */ /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/ .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; } /* Rules to colour arrows */ .tipsy-arrow-n { border-bottom-color: #000; } .tipsy-arrow-s { border-top-color: #000; } .tipsy-arrow-e { border-left-color: #000; } .tipsy-arrow-w { border-right-color: #000; } .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; } .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,258 @@ // tipsy, facebook style tooltips for jquery // version 1.0.0a // (c) 2008-2010 jason frame [[email protected]] // released under the MIT license (function($) { function maybeCall(thing, ctx) { return (typeof thing == 'function') ? (thing.call(ctx)) : thing; }; function isElementInDOM(ele) { while (ele = ele.parentNode) { if (ele == document) return true; } return false; }; function Tipsy(element, options) { this.$element = $(element); this.options = options; this.enabled = true; this.fixTitle(); }; Tipsy.prototype = { show: function() { var title = this.getTitle(); if (title && this.enabled) { var $tip = this.tip(); $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); var pos = $.extend({}, this.$element.offset(), { width: this.$element[0].offsetWidth, height: this.$element[0].offsetHeight }); var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight, gravity = maybeCall(this.options.gravity, this.$element[0]); var tp; switch (gravity.charAt(0)) { case 'n': tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; break; case 's': tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; break; case 'e': tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}; break; case 'w': tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}; break; } if (gravity.length == 2) { if (gravity.charAt(1) == 'w') { tp.left = pos.left + pos.width / 2 - 15; } else { tp.left = pos.left + pos.width / 2 - actualWidth + 15; } } $tip.css(tp).addClass('tipsy-' + gravity); $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0); if (this.options.className) { $tip.addClass(maybeCall(this.options.className, this.$element[0])); } if (this.options.fade) { $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity}); } else { $tip.css({visibility: 'visible', opacity: this.options.opacity}); } } }, hide: function() { if (this.options.fade) { this.tip().stop().fadeOut(function() { $(this).remove(); }); } else { this.tip().remove(); } }, fixTitle: function() { var $e = this.$element; if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') { $e.attr('original-title', $e.attr('title') || '').removeAttr('title'); } }, getTitle: function() { var title, $e = this.$element, o = this.options; this.fixTitle(); var title, o = this.options; if (typeof o.title == 'string') { title = $e.attr(o.title == 'title' ? 'original-title' : o.title); } else if (typeof o.title == 'function') { title = o.title.call($e[0]); } title = ('' + title).replace(/(^\s*|\s*$)/, ""); return title || o.fallback; }, tip: function() { if (!this.$tip) { this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>'); this.$tip.data('tipsy-pointee', this.$element[0]); } return this.$tip; }, validate: function() { if (!this.$element[0].parentNode) { this.hide(); this.$element = null; this.options = null; } }, enable: function() { this.enabled = true; }, disable: function() { this.enabled = false; }, toggleEnabled: function() { this.enabled = !this.enabled; } }; $.fn.tipsy = function(options) { if (options === true) { return this.data('tipsy'); } else if (typeof options == 'string') { var tipsy = this.data('tipsy'); if (tipsy) tipsy[options](); return this; } options = $.extend({}, $.fn.tipsy.defaults, options); function get(ele) { var tipsy = $.data(ele, 'tipsy'); if (!tipsy) { tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options)); $.data(ele, 'tipsy', tipsy); } return tipsy; } function enter() { var tipsy = get(this); tipsy.hoverState = 'in'; if (options.delayIn == 0) { tipsy.show(); } else { tipsy.fixTitle(); setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn); } }; function leave() { var tipsy = get(this); tipsy.hoverState = 'out'; if (options.delayOut == 0) { tipsy.hide(); } else { setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut); } }; if (!options.live) this.each(function() { get(this); }); if (options.trigger != 'manual') { var binder = options.live ? 'live' : 'bind', eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus', eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'; this[binder](eventIn, enter)[binder](eventOut, leave); } return this; }; $.fn.tipsy.defaults = { className: null, delayIn: 0, delayOut: 0, fade: false, fallback: '', gravity: 'n', html: false, live: false, offset: 0, opacity: 0.8, title: 'title', trigger: 'hover' }; $.fn.tipsy.revalidate = function() { $('.tipsy').each(function() { var pointee = $.data(this, 'tipsy-pointee'); if (!pointee || !isElementInDOM(pointee)) { $(this).remove(); } }); }; // Overwrite this method to provide options on a per-element basis. // For example, you could store the gravity in a 'tipsy-gravity' attribute: // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' }); // (remember - do not modify 'options' in place!) $.fn.tipsy.elementOptions = function(ele, options) { return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; }; $.fn.tipsy.autoNS = function() { return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; }; $.fn.tipsy.autoWE = function() { return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; }; /** * yields a closure of the supplied parameters, producing a function that takes * no arguments and is suitable for use as an autogravity function like so: * * @param margin (int) - distance from the viewable region edge that an * element should be before setting its tooltip's gravity to be away * from that edge. * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer * if there are no viewable region edges effecting the tooltip's * gravity. It will try to vary from this minimally, for example, * if 'sw' is preferred and an element is near the right viewable * region edge, but not the top edge, it will set the gravity for * that element's tooltip to be 'se', preserving the southern * component. */ $.fn.tipsy.autoBounds = function(margin, prefer) { return function() { var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, boundTop = $(document).scrollTop() + margin, boundLeft = $(document).scrollLeft() + margin, $this = $(this); if ($this.offset().top < boundTop) dir.ns = 'n'; if ($this.offset().left < boundLeft) dir.ew = 'w'; if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e'; if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's'; return dir.ns + (dir.ew ? dir.ew : ''); } }; })(jQuery);