DiaColloDB-WWW

 view release on metacpan or  search on metacpan

share/htdocs/diacollo.js  view on Meta::CPAN

//-*- Mode: Javascript; coding: utf-8; -*-
//
// File: diacollo.js
// Author: Bryan Jurish <moocow@cpan.org>
// Description: client-side diacollo callbacks & visualization routines
//
// WARNING
//  The following code is hacky, messy, sloppy, ugly, and otherwise generally sub-optimal.
//  Patches & improvements welcome.
//  Continue at your own risk.
//

//-- user query params
var user_query   = {};
var user_format  = null; //-- save format request, e.g. for motion-charts
var qinfo        = null; //-- query info, set e.g. by html mode
var responseData = null; //-- cached response data, for browser-friendly 'save as'

//-- timing
var ttk_elapsed = 0;
var dcp_t0 = 0;

//----------------------------------------------------------------------
// profile queries params
var dcp_url_base = ".";
var dcp_url_local = "./profile.perl";
var dcp_params_default = {
    "query" : null,
    "date"  : null,
    "slice" : null,
    "score" : null,
    "kbest" : null,
    "cutoff" : null,
    "diff" : null,
    "global" : null,
    "onepass" : null,
    "profile" : null,
    "format" : "text",
    "debug" : 0,
    "groupby": null,
    "eps" : 0
};
var dynformats = {
    "gmotion" : true,
    "hichart" : true,
    "bubble" : true,
    "cloud": true
};
var scoreNames = {
    'f': 'Frequency',
    'fm': 'Frequency per Million',
    'lf': 'log Frequency',
    'lfm': 'log Frequency per Million',
    'milf': 'Pointwise Mutual Information * log Frequency',
    'mi1': 'Pointwise Mutual Information',
    'mi3': 'Mutual Information^3',
    'ld': 'log Dice',
    'll': 'log Likelihood'
};

//----------------------------------------------------------------------
function dqReady() {
    //-- set preliminary timing info
    $(".elapsed").text("~" + String(ttk_elapsed) + "\u00a0sec");

share/htdocs/diacollo.js  view on Meta::CPAN

		    idata.f1  = [];
		    idata.f2  = [];
		    idata.f12 = [];
		}
	    }

	    idata.score[pi] = iscore = pscores[item];
	    idata.value[pi] = ivalue = dcpScoreValue(iscore);
	    idata.avalue[pi] = avalue = Math.abs(iscore)/amax;
	    idata.sizes[pi] = dcpItemSize(idata,pi);
	    idata.opacity[pi] = 1;

	    if (isDiff) {
		idata.N1[pi] = ffmt(p.prf1.N);
		idata.N2[pi] = ffmt(p.prf2.N);
		idata.af1[pi] = ffmt(p.prf1.f1);
		idata.bf1[pi] = ffmt(p.prf2.f1);
		idata.af2[pi] = ffmt(p.prf1.f2[item]);
		idata.bf2[pi] = ffmt(p.prf2.f2[item]);
		idata.af12[pi] = ffmt(p.prf1.f12[item]);
		idata.bf12[pi] = ffmt(p.prf2.f12[item]);
		idata.ascore[pi] = sfmt(p.prf1[p.score][item]);
		idata.bscore[pi] = sfmt(p.prf2[p.score][item]);
	    } else {
		idata.N[pi]   = ffmt(p.N);
		idata.f1[pi]  = ffmt(p.f1);
		idata.f2[pi]  = ffmt(p.f2[item]);
		idata.f12[pi] = ffmt(p.f12[item]);
	    }

	}
    }

    //-- check again for empty data-set (b/c we might have empty profiles)
    if (items.length == 0) {
	dcpErrorMsg("Error: no items to display!");
	return null;
    }


    //-- setup date-interpolator
    var dltuples = dlabels.map(function(l) { return l.split("-"); });
    var dln      = d3.max(dltuples, function(tup) { return tup.length; });
    if (dln <= 1) {
	//-- scalar date-labels: easy interpolation
	dcpDateInterp = function(di) { return Math.round(linterp(dlabels,di)); };
    } else {
	//-- multi-component date-labels: build tuple-wise interpolator
	var dlscale = [];
	for (i=0; i < dln; ++i) {
	    dlscale[i] = d3.scale.linear()
		.domain(dltuples.map(function(e,ei) { return ei }))
		.range(dltuples.map(function(e) { return e[i] }))
		.clamp(true);
	}
	dcpDateInterp = function(di) {
	    return dlscale.map(function(s) { return Math.round(s(di)) }).join("-");
	};
    }

    //-- setup callbacks
    if (isBubble) {
	brushInterp = dcpForceInterp;
	brushSnap   = dcpForceSnap;
    }
    if (isCloud) {
	brushInterp = dcpCloudInterp;
	brushSnap   = dcpCloudSnap;
    }

    //-- initialize current subprofile index (dcur) from URL fragment
    var fragment = locFragment(window.location);
    if (fragment != "" && dlabels.indexOf(fragment) >= 0) {
	dcur = dlabels.indexOf(fragment);
    } else {
	dcur = 0;
    }

    return data;
}

//----------------------------------------------------------------------
// d3: common: interpolating accessors
function vinterp(frac, x0,x1, missing) {
    if (missing==null) missing=0;
    return ((1.0-frac)*(x0==null ? missing : x0)) + (frac*(x1==null ? missing : x1));
}
function linterp(l,pos,missing) {
    return vinterp(pos-Math.floor(pos), l[Math.floor(pos)], l[Math.ceil(pos)], missing);
}
function dcpItemScore(item,pos) { return linterp(item.score, pos); }
function dcpItemValue(item,pos) { return linterp(item.value, pos, dcpValueNull); }
function dcpItemAbsValue(item,pos) { return linterp(item.avalue, pos); }
//dcpItemSize : function variable
function dcpItemScale(item,pos) { return dcpItemSize(item,pos) / item.maxSize; }

function dcpScoreValue(score) {
    return (score-dcpScoreRange[0]) / (dcpScoreRange[1]-dcpScoreRange[0]);
}

//-- dcpItemSat, dcpItemVal: for "old" rainbow-style colors
//   + green takes up too much space in these for some reason (~ 4 score points on for diff [-8..8])
//   + better differentiation using colorbrewer colors and d3 scale, not as pretty for html though
var dcpItemSat = 1;
var dcpItemVal = 1;
function dcpItemColor(item,pos) { return heatcolorf(dcpItemValue(item,pos), dcpItemSat, dcpItemVal); }
function dcpMinColor() { return heatcolorf(0, dcpItemSat, dcpItemVal); }
function dcpMaxColor() { return heatcolorf(1, dcpItemSat, dcpItemVal); }

function dcpItemOpacity(item,pos,max) {
    return (max==null ? 1 : max)*linterp(item.opacity,pos);
}

// interpolator = dcpDateInterpolator(dlabel0,dlabel1)
//  + returned function is called as "interpolatedDateLabel = interpolator(t)" with 0 <= t <= 1
function dcpDateInterpolator(dlabel0,dlabel1) {
    var d0 = dlabel0.split("-");
    var d1 = dlabel1.split("-");
    if (d0.length==1) {
	return d3.interpolateRound(Number(d0[0]),Number(d1[0]));
    } else {

share/htdocs/diacollo.js  view on Meta::CPAN

	.attr("id",function(d,i) { return "tick"+i; });
    ticks.each(function(d,i) { d3.select("#tick"+i+" text").text(d); });
    //svg.select(".tick:first-of-type text").style("text-anchor","start");
    //svg.select(".tick:last-of-type text").style("text-anchor","end");

    //-- check for tick overflow
    var nticks         = ticks.size();
    var tickLabelWidth = [];
    ticks.each(function(d,i) { tickLabelWidth.push(d3.select("#tick"+i+" text").node().getComputedTextLength()); });
    var tickPad      = 5;
    var tickWidthMax = d3.max(tickLabelWidth) + tickPad;
    var tickmod = 1;
    while ( (nticks/tickmod)*tickWidthMax >= opts.width ) {
	++tickmod;
    }
    ticks.each(function(d,i) {
	if ((i % tickmod) != 0) {
	    d3.select("#tick"+i).classed("minor",true);
	}
    });

    //-- setup slider
    var slider = svg.append("g")
	.attr("class", "slider")
	.call(brush);

    slider.selectAll(".extent,.resize")
	.remove();
    slider.select(".background")
	.attr("height", height);
	

    var handle = bhandle = slider.append("g")
	.attr("class", "handle")
	.attr("transform", "translate(0,0)");
    handle.append("circle")
	.attr("cx",0)
	.attr("cy",dy)
	.attr("r", 8);
    handle.append("text")
	.attr("x",0)
	.attr("y",10)
	.text("");

    var x0 = dcur; //xscale(dlabels[0]);
    slider
	.call(brush.event)		//-- d3.v4: ???
	.call(brush.extent([x0,x0]))
	.call(brush.event);		//-- d3.v4: ???

    //-- mouse events: hover
    svg
	.on("mouseenter", function(b) { svg.classed("hovering",true); })
	.on("mouseleave", function(b) { svg.classed("hovering",false); });

    //$("#profileDataD3").show(); //-- debug
    return brush;
}

//--------------------------------------------------------------
// d3: brush-slider: callbacks
function brushDebug(value,label) {
    if (label==null) label="debug";
    var msg = "[" + label + "] value=" + value;
    debug_log(msg);
    d3.select("#brushVal").text(msg);
}

function dcpOnBrushStart(force) {
    //var value = dbrush.extent()[0];
    if (force || d3.event.sourceEvent) { // not a programmatic event
	dcpPlay(false);
	bsvg.classed("brushing",true);	
    }
}

function dcpBrushMove(value,dur,easeby) {
    var nearest = Math.round(value);
    d3.selectAll(".tick text").classed("selected",false);
    d3.select(".tick:nth-of-type("+(nearest+1)+") text").classed("selected",true);
    if (dur==null || easeby==null) {
	bhandle.transition().duration(0);
	bhandle
	    .attr("transform","translate("+dbrush.x()(value)+",0)")
	    .select("text")
	    .text(dcpDateInterp(value))
	;
    } else {
	bhandle.transition()
	    .duration(dur)
	    .ease(easeby)
	    .attr("transform","translate("+dbrush.x()(value)+",0)")
	    .select("text")
	    .tween("handle-text", function() {
		var interp = dcpDateInterpolator(this.textContent, String(dcpDateInterp(value)));
		return function(t) { this.textContent = interp(t); };
	    })
	;
    }
    dcur = value;
    dbrush.extent([value,value]);
    brushInterp(value,dur,easeby);
}

function dcpOnBrush() {
    var value  = dbrush.extent()[0];
    //brushDebug(value,"brush");
    if (d3.event.sourceEvent) { // not a programmatic event
	value = dbrush.x().invert(d3.mouse(this)[0]);
	bsvg.classed("brushing",true);
    }
    dcpBrushMove(value);
}

function dcpOnBrushEnd(force,dur,easeby,dosnap) {
    if (!force && !d3.event.sourceEvent) return; // only transition after input
    if (dosnap==null) dosnap=true;
    var value  = dbrush.extent()[0];
    var snapto = Math.round(value);
    var moveto = dosnap ? snapto : value;

share/htdocs/diacollo.js  view on Meta::CPAN

	.call(sbrush.event)
	.call(sbrush.extent([s0,s0]))
	.call(sbrush.event);

    //-- initialize play/pause visibility
    dcpPlay(false);

    return svg;
}

function d3SpeedBrushDebug(label) {
    //debug_log(label+": val=" + speedBrush.extent()[0] + " ~ " + speedScale(speedBrush.extent()[0]));
    return;
}

function d3SpeedBrushMove(value,dur,easeby) {
    dcpSpeed = value;
    speedBrush.extent([value,value]);
    var voff = speedHandle.attr("height")/2;
    var fmt  = d3.format(".3f");
    if (dur==null || easeby==null) {
	speedHandle.attr("y",speedBrush.y()(value)-voff);
    } else {
	speedHandle.transition()
	    .duration(dur)
	    .ease(easeby)
	    .attr("y",speedBrush.y()(value)-voff);
    }
    $("#curspeed").text(fmt(value)+"x"); //-- +"\u00d7"
    if (dcpPlaying) dcpPlay(true,true); //-- re-compute play animation
}

function d3SpeedSet(value,dur,easeby) {
    //debug_log("d3SpeedSet("+JSON.stringify({"value":value,"dur":dur,"easeby":easeby})+")");
    dcpSpeed = value;
    d3SpeedBrushMove(value,dur,easeby);
}

function d3OnSpeedBrushStart() {
    //d3SpeedBrushDebug("d3OnSpeedBrushStart()");
    if (d3.event.sourceEvent) { // not a programmatic event
	speedNode.classed("brushing",true);
    }
}

function d3OnSpeedBrush() {
    //d3SpeedBrushDebug("d3OnSpeedBrush()");
    var value  = speedBrush.extent()[0];
    if (d3.event.sourceEvent) { // not a programmatic event
	value = speedBrush.y().invert(d3.mouse(this)[1]);
    }
    d3SpeedBrushMove(value);
}

function d3OnSpeedBrushEnd() {
    //d3SpeedBrushDebug("d3OnSpeedBrushEnd()");
    speedNode.classed("brushing",false);
}

//----------------------------------------------------------------------
// d3: play/pause transport: callbacks
function dcpPlay(playing,force) {
    if (playing==null) playing = dcpPlaying;
    if (force==null) force=false;
    //exportMenuHide(0);

    //-- setup buttons
    var btn = d3.selectAll(".btn");
    btn.selectAll(".play").style("opacity",Number(!playing));
    btn.selectAll(".pause,.stop").style("opacity",Number(playing));

    if (!force && playing==dcpPlaying) return;
    dcpPlaying = playing;

    //-- maybe start playing
    if (playing) {
	//-- play
	if (dlabels.length < 2) {
	    alert("Play animation only available for multi-slice profiles!");
	    return dcpPlay(false);
	}
	var pos0 = dbrush.extent()[0];
	var pos1 = dlabels.length-1;
	if (pos0 == pos1) { pos0 = 0; }
	var totaldur = 15000 / dcpSpeed; //-- play-length for total sequence (in ms; google motion chart ~15s; [2s=.133x .. 40s=2.6x])
	var interp   = d3.interpolate(pos0,pos1);
	d3.select("#d3slider")
	    .transition()
	    .duration((totaldur/(dlabels.length-1))*(pos1-pos0))
	    .ease("linear")
	    .tween("brush", function() {
		//-- hack: check dcpPlaying to avoid transition interference (keybd interrupting play)
		return function(t) { if (dcpPlaying) dcpBrushMove(interp(t)); }
	    })
	    .each("end",function() { dcpPlay(false); });
    }
    else {
	//-- stop
	d3.select("#d3slider")
	    .transition()
	    .duration(0)
	    .tween("brush", function() { return function(t) { ; } })
	    //.call(dbrush.event)
	;
    }
}

//----------------------------------------------------------------------
// d3: legend (color-scale / "y axis")
function dcpLegend(parent_selector,opts) {
    //-- defaults
    if (opts==null) opts={};
    if (opts.id==null) { opts.id="d3legend"; }
    if (opts.width==null) { opts.width = 50; }
    if (opts.height==null) { opts.height = 450; }
    if (opts.opacity==null) { opts.opacity = 1; }
    var margin = opts.margin==null ? {} : opts.margin;
    if (margin.left==null) margin.left = 0;
    if (margin.right==null) margin.right = 0;
    if (margin.top==null) margin.top = 0;
    if (margin.bottom==null) margin.bottom = 0;

share/htdocs/diacollo.js  view on Meta::CPAN

	item.maxSize = Math.round( d3.max(item.sizes) );
    });

    //-- rotations
    var rotations 
	= [0];
	//= [0,90];
        //= [0,90,45,-45];
	//= [0,45,-45];
       //= [45,-45];
    var main  = d3.select("#d3main");
    var cloud = dcloud = d3.layout.cloud()
	.size([Number(psvg.attr("width")),Number(psvg.attr("height"))+25]) //-- weird unused space at bottom: tweaking height+25
	.words(items)
	.padding(1)
	.random(xrandom)
	//.rotate(0)
	//.rotate(function(d) { return rotations[d.id % rotations.length]; })
	.rotate(function() { return rotations[~~(xrandom() * rotations.length)]; })
	.font(dcpCloudFont.family)
	.fontWeight(dcpCloudFont.weight)
	.fontStyle(dcpCloudFont.style)
	.fontSize(function(d) { return d.maxSize; })
	.timeInterval(50)
	.on("end",dcpCloudEnd);

    //dcpErrorMsg("-- work in progress --");
    $(".rawURL").hide();
    $("#profileDataD3").show();
    cloud.start();
    return;
}
/*-- test color-scale:
function cboxes(scale) {
    var boxes = d3.selectAll(".y.axis .boxes rect");
    var imax  = boxes.size();

    boxes.style("fill", function(d,i) {
	//console.log("color("+i+"/imax:"+(i/imax)+"~"+(i/imax*scale.range().length)+")="+x);
	debug_log("color("+i+"/imax:"+(i/imax)+"~"+(i/imax)+")="+scale(i/imax));
	return scale(i/imax);
    });
}
function cscale(colors,interp) {
    if (interp==null) interp = d3.interpolateRgb;
    var scale = d3.scale.linear()
	.domain(d3.range(0,colors.length).map(function(i) { return i/(colors.length-1); }))
	.range(colors)
	.interpolate(interp);
    return scale;
}
//-- color brewer colors; see http://colorbrewer2.org/
//-- diff (divergent)
redgb4 = ["rgb(215,25,28)", "rgb(253,174,97)", "rgb(171,221,164)", "rgb(43,131,186)"]
redgb8 = ["rgb(213,62,79)", "rgb(244,109,67)", "rgb(253,174,97)", "rgb(254,224,139)", "rgb(230,245,152)", "rgb(171,221,164)", "rgb(102,194,165)", "rgb(50,136,189)"]
//-- sequential
red4 = ['rgb(254,240,217)','rgb(253,204,138)','rgb(252,141,89)','rgb(215,48,31)']
*/

//--------------------------------------------------------------
// d3: cloud: callbacks
function dcpCloudEnd(placedWords,bounds) {
    var svg = d3.select(d3BodySelector);
    svg
	.append("g")
	.attr("transform", "translate(" + dcloud.size()[0]/2 + "," + dcloud.size()[1]/2 + ")");

    //-- warn about placement errors and maybe recompute
    var nbad = items.length - placedWords.length;
    if (nbad > 0) {
	var cls = "warning";
	var msg = "Warning: "+nbad+" of "+items.length+" data point(s) could not be displayed";
	var tryRescale = (dcpCloudScale > 0.125);
	if (tryRescale) {
	    dcpCloudScale *= 0.5;
	    var trimKeys = ["onScreen","x","x0","x1","xoff","y","y0","y1","yoff","width","height","size","rotate","font","hasText","padding","weight","style"];
	    items.forEach(function(d) {
		trimKeys.forEach(function(k) { delete d[k]; });
	    });
	    msg += " -- re-computing with scale="+dcpCloudScale;
	    cls += " loading";
	} else {
	    msg += " at minimum scale="+dcpCloudScale;
	}
	debug_log(msg);
	dcpStatusMsg(cls,msg).hide().fadeIn();
	if (tryRescale) {
	    return dcpCloudSetup(svg);
	}
	else  {
	    $("#status").fadeOut(5000);
	}
    } else if (dcpCloudScale == 1) {
	dcpClearMsg();
    }  else {
	dcpInfoMsg("Placed all "+items.length+" data point(s) at scale="+dcpCloudScale).fadeOut(2000);
    }

    //-- record final placement errors
    placedWords.forEach(function(d) { d.onScreen=true; });

    //dcpCloudDebug(); return; //-- DEBUG
    //dcpCloudDebug(0,'linear',0,true);  return; //-- we don't get collisions if we do dcpCloudDebug() before dcpCouldInterp(): why???

    //-- draw data
    dcpCloudInterp(dcur,1000,"cubic-in-out");
    //dcpBrushMove(dcur,1000,"cubic-in-out");

    //dcpShowPrefetchHint(); //-- show "pre-fetched data" hint if appropriate
}

//-- debug: bbox string for getBoundingClientRect()
function bbstr(bb) {
    return '[' + [bb.left,bb.top,bb.right,bb.bottom].join(',') + ']';
}


//--------------------------------------------------------------
// d3: cloud: update (snap)
function dcpCloudSnap(value) {
    if (value==null) value=dcur;



( run in 1.572 second using v1.01-cache-2.11-cpan-d8267643d1d )