/*********************************************************************************** * * * cal: * Makes and updates the calendar graphic, and attaches the handlers to it for making tagclouds for specific periods. * Everything is based on the cal.calArr array, which stores each days (in local time) and the posts that go with it. * * The cal.win object keeps track of the days that are in the selected window; those are the ones that the tagcloud * logic is interested in. * * ************************************************************************************/ //settings: cal = { //the settings can be customized: ul: "#cal ul.calendar", //the place in the markup where the calendar will go numberOfDays: 30, //you can display any number of days (assuming, of course, you actually have the information server-side) ajaxUrl: './ajax.php', selWidth: 7, //the default for how many days get selected at once //these shouldn't be changed: active:true, calArr: [], blogName:'', allowDisplayUpdate:false //we want to lock the tagcloud animation until after other animations have run. } /*Make the array of caledar days, with start and end timestamps for each. *********************************************************************************/ cal.makeCalArr = function() { var d = new Date(); d.setDate(d.getDate() + 1); for (var i = 0; i < cal.numberOfDays; i++) { cal.calArr[i] = {}; d.setDate(d.getDate() - 1); //gets the utc timestamp for local midnight last night. d.setHours(0); d.setMinutes(0); d.setSeconds(0); d.setMilliseconds(0); cal.calArr[i]['dayStart'] = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()) / 1000; //gets the utc timestamp for local 23:59:59 tonight. d.setHours(23); d.setMinutes(59); d.setSeconds(59); cal.calArr[i]['dayEnd'] = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()) / 1000; } } //turns the array into json. lighter than using a whole library. cal.calArr.stringify = function() { len = this.length; var str = '['; for (var i = 0; i < len; i++) { if (!i == 0) str += ","; str += '{"dayStart":"' + this[i]['dayStart'] + '","dayEnd":"' + this[i]['dayEnd'] + '"}'; } str += ']'; return str; } /* Request the calendar data (posts per day); append the calendar and format it. *********************************************************************************/ cal.makeCal = function() { $.post(cal.ajaxUrl, {calArr:cal.calArr.stringify(), blogName:cal.blogName, account:cal.account}, function(json) { var liWidth = 100 / cal.calArr.length; //we've already made a calendar; we're updating it if ($("li", cal.ul).length) { var len = cal.calArr.length for (var i = len - 1; i >= 0; i--) { $("li", cal.ul).eq(len - 1 - i).find("dd.total").text(json[i].length) cal.calArr[i]['posts'] = json[i]; } //this is just for the first run: } else { for (var i = cal.calArr.length - 1; i >= 0; i--) { //add the posts to the cal array, to go with the start and end timestamps cal.calArr[i]['posts'] = json[i]; //print the list items $("
  • "+i+"") .append("

    ") .append("
    Total posts
    ") .find("dl").append("
    " + json[i].length + '
    ') .end() .width(liWidth + "%") .appendTo(cal.ul); } $("li", cal.ul).click(function(e){ cal.clickHandler(e); }); $("#blog-names li").click(function(e) { cal.filterByBlog(e); }); $("#blog-names h3 span").click(function(e) { cal.filterByBlog(e); }); cal.hoverHandler(); } cal.animate_cal(); },"json"); } /* methods for formatting the calendar: *********************************************************************************/ //sets the max and min value of posts in the cal ul cal.getMaxMin = function() { var totals = new Array; $("dd", cal.ul).each(function(){ totals.push($(this).text()); }); this.maxi = Math.max.apply(null, totals); this.mini = Math.min.apply(null, totals); } //animates the height of the bars cal.animate_cal = function() { cal.getMaxMin(); cal.allowDisplayUpdate = false; //we don't want the display trying to animate while we animate the height var totals = new Array; $("dd", cal.ul).each(function(){totals.push($(this).text())}); var normFactor = 100 / cal.maxi; var ranOnce = false; $("dd", cal.ul).each(function(){ //we want to animate each bar: var thisHeight = $(this).text() * normFactor; $(this).animate( {height:thisHeight+"%"}, 1500, function() {//there's a bunch of stuff we want to do only after the animation is complete: if (ranOnce) return false; //we don't want to run this callback for every dd cal.displayBenchmarks(); if (cal.win.getFirst()) { //if there's a window selected, we must want a new tagcloud cal.allowDisplayUpdate = true; //ok, now we're done animating. cal.getWords(); } ranOnce = true; }); }); } //shows the number of posts for the highest and lowest days on the chart cal.displayBenchmarks = function() { var minFound = false; var maxFound = false; var lowest = (cal.mini == 0) ? 1 : cal.mini; $("dd", cal.ul).each(function(){ $(this).removeClass("benchmark") if ($(this).text() == cal.maxi && !maxFound) { $(this).addClass("benchmark"); maxFound = true; //we only want to show one of each; more is confusing. } if ($(this).text() == lowest && !minFound) { $(this).addClass("benchmark"); minFound = true; } }) } /* methods for selecting a part of the calendar: *********************************************************************************/ //manages the selected day and their data cal.win = { days:{}, set:function(center) { span = Math.round(cal.selWidth / 2); //rounds .5 up. //the selection shouldn't be able to fall off the ends: center = Math.max(center, span - 1); center = Math.min(center, cal.calArr.length - span); var inWin = {}; $.each(cal.calArr, function(i, val) { if (i > center - span && i < center + span) inWin[i] = val; }); cal.win.days = inWin; }, getFirst: function() { for (k in cal.win.days) { var kNum = parseInt(k); if (!(kNum <= largestKey) && kNum >= 0) var largestKey = k } return cal.win.days[largestKey]; }, getLast: function() { for (k in cal.win.days) { var kNum = parseInt(k); if (!(kNum >= smallestKey) && kNum >= 0) var smallestKey = k; } return cal.win.days[smallestKey]; }, stringify: function() { var allPosts = []; $.each(cal.win.days, function(i, val) { allPosts = allPosts.concat(this.posts); }); return "[" + allPosts.join() +"]"; }, countPosts: function() { var postsStr = this.stringify(); if (!postsStr.match(/\d/)) return 0; if (!postsStr.match(/,/)) return 1; return postsStr.match(/,/g).length + 1; }, getRange: function(sep) {//return a formatted string showing the timespan of the window. var d1 = new Date(cal.win.getFirst().dayStart * 1000); var d2 = new Date(cal.win.getLast().dayEnd * 1000); //if there's just one day, don't return a range: if (d1.getDay() == d2.getDay()) return d1.monthName() + '. ' + d2.getDate(); //if there are multiple days: if (d1.getMonth() == d2.getMonth()) { return d1.monthName() + '. ' + d1.getDate() + sep + d2.getDate(); } else { return d1.monthName() + '. ' + d1.getDate() + sep + d2.monthName() + '. ' + d2.getDate(); } } } //check the current window; return a formatted string showing the range. //on hover, set the the window of active days and assign styles to the posts in those days cal.hoverHandler = function() { $("li", cal.ul).bind("mouseover", function(){ if (!cal.active) return; //we want to disable this sometimes //find the center of the selection, so the arrow can point there: cal.center = this; cal.labelOffset(this); //find which days are in the sample, so we can highlight them: //first, find which days in the cal.calArr are in the selection; this gives us the cal.win.days array. cal.win.set(parseInt($(this).find("span.day").text())); //then, add the class to each calendar day that's in the selections array: //this lags too much. I tried finding the position of this, then using gt:() and lt:() ; didn't seem to be any faster. $.each(cal.win.days, function(i) { $(cal.ul).find("span.day:content("+i+")").parent().addClass("window"); }); //change the h3 of the center li to show the dates in the selection. It might be better to //just make and append a single h3, then position it where we want. var winStart = cal.win.getFirst().dayStart; var winEnd = cal.win.getLast().dayEnd; $(cal.center).addClass("center").find("h3").text(cal.win.getRange(" - ")) }); $("li", cal.ul).bind("mouseout", function() { $(this).siblings().andSelf().removeClass("window center"); }); } //makes the labels under each sample window look purty; without this, when they are on the end //they overlap the edges of the calendar. //maybe i should redo this so that there's just one h3 sitting below the calendar, that gets relatively // positioned to be under the right day... cal.labelOffset = function(li) { var h3width = $("h3", li).width() liWidth = $(li).width(); var offsetR = liWidth * $(li).nextAll().length; var offsetL = liWidth * $(li).prevAll().length; var h3offset; if (offsetR < h3width / 2) { h3offset = offsetR - h3width / 2; $("h3", li).css( {left: h3offset, textAlign:"right"} ); } else if (offsetL < h3width / 2) { h3offset = h3width / 2 - offsetL; $("h3", li).css( {left: h3offset, textAlign:"left"} ); } else { var h3offset = 0; } } //runs the animation showing the selection; calls the ajax function to get the selection info cal.clickHandler = function(e) { if (!cal.active) return; //we want to disable this sometimes. if (cal.win.countPosts() == 0) return; //no point in sending nothing. cal.allowDisplayUpdate = false; cal.newDataReady = false; cal.active = false; //reactiate this in the callback after the tagcloud animation. if (!$("#vis").hasClass("live")) { //only runs on the first click $("#date-info").animate( {minHeight:"35em"}, 3500 ); $("#triangle").parent().animate( { height:"1em"}, 500 ); } //prettify the selected list items. $("li", cal.ul).removeClass("selected").end().find(".window").addClass("selected").removeClass("window"); $(".dropShadow").remove(); $("li.selected dd") .dropShadow( { //in the jquery plugins file. opacity: .2 }); $("#vis").addClass("live"); //insert the animated 'loading...' graphic: $("#date-info .header").empty().append("

    loading

    "); //add and move the pointer graphic var liPos = $(cal.center).position(); var liCenter = (liPos.left + ($(cal.center).width() / 2)); var triWidth = $("#triangle").width(); if (liCenter + (triWidth / 2) > $("#vis").width()) { //it's hanging off the right edge var newTriPos = $("#vis").width() - triWidth; } else if (liCenter - (triWidth / 2) < 0) { var newTriPos = 0; //off the left edge } else { var newTriPos = liCenter - (triWidth / 2); } var percentWidth = (newTriPos / $("#cal").width() * 100) + "%" //updating while we move the pointer breaks the animation. But, if we wait until the pointer is done //to make the ajax call, we waste that half-sec doing nothing. Plus, we have to allow for when the response //takes more than half a second: $("#triangle").animate( {left:percentWidth }, 500, function() { if (cal.newDataReady) cal.updateDisplay(); //getWords() finished first and has loaded the new data for us; we can update. cal.allowDisplayUpdate = true; }); //make the ajax call to get the date info cal.getWords(); } /* ways for the user to control the display and content of the calendar: *********************************************************************************/ cal.setControls = function(loc) { $("a", loc).click(function() { $("a", loc).removeClass("selected"); $(this).addClass("selected"); //if the user wants all the days, no need to make here click the calendar: //we call the hover and click handlers right away. if ($(this).text() == "all") { cal.selWidth = cal.numberOfDays + 1; cal.win.set() $("li", cal.ul) .eq(Math.ceil(cal.numberOfDays / 2)) .trigger("mouseover").click(); } else { //for a specified length, we just change the selection width property and wait for a click or hover. cal.selWidth = $(this).text(); cal.win.set(); //set the array that contains all the selected days. } return false; }); halp.sampleSize($("h2", loc)); } cal.filterByBlog = function(e) { $("div.dropShadow").remove(); //get the span text, whether the click was on that or elsewhere in the li var span$ = $(e.target).find("span").andSelf().filter("span"); //wow, this is so ugly. no time. $("#blog-names").find(".selected").removeClass("selected"); span$.parent().addClass("selected") cal.blogName = (span$.text() == "(all)") ? false : span$.text(); cal.makeCal(); } /* get and display the information from a calendar selection *************************************************************************************************************/ cal.getWords = function() { $.post(cal.ajaxUrl, {posts:cal.win.stringify(), allWords:true, account:cal.account}, function(rock) { if (rock.tagcloud.length == 0) { } //update data for the tagcloud newTc.tagcloud = rock.tagcloud; newTc.info = rock.info; //if the the pointer has finished by this time, update; if not, we'll wait for the changeTimeWindow handler to deal with it. cal.newDataReady = true; if (cal.allowDisplayUpdate) cal.updateDisplay(); }, "json"); } cal.updateDisplay = function() { //run the functions that update everything newTc.updateHeader("#date-info .header"); newTc.updateTc("#tagcloud"); //set up the handlers for drilling down into info: $("#tagcloud li a").unbind("click") $("#tagcloud li a").click(function() {wordInfo.zoomIn(this); return false;}); }