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 )